{"id":10021033,"date":"2025-10-15T07:43:28","date_gmt":"2025-10-14T23:43:28","guid":{"rendered":"https:\/\/itmax.cn\/?p=10021033"},"modified":"2025-10-15T07:43:28","modified_gmt":"2025-10-14T23:43:28","slug":"spring-boot-%e9%9b%86%e6%88%90%e5%85%8d%e8%b4%b9%e7%9a%84-edgetts-%e5%ae%9e%e7%8e%b0%e6%96%87%e6%9c%ac%e8%bd%ac%e8%af%ad%e9%9f%b3","status":"publish","type":"post","link":"https:\/\/itmax.cn\/?p=10021033","title":{"rendered":"Spring Boot \u96c6\u6210\u514d\u8d39\u7684 EdgeTTS \u5b9e\u73b0\u6587\u672c\u8f6c\u8bed\u97f3"},"content":{"rendered":"<p>\u5728\u9700\u8981\u6587\u672c\u8f6c\u8bed\u97f3\uff08TTS\uff09\u7684\u5e94\u7528\u573a\u666f\u4e2d\uff08\u5982\u8bed\u97f3\u52a9\u624b\u3001\u8bed\u97f3\u901a\u77e5\u3001\u5185\u5bb9\u64ad\u62a5\u7b49\uff09\uff0cJava\u751f\u6001\u7f3a\u5c11\u7c7b\u4f3cPython\u751f\u6001\u7684Edge TTS \u5ba2\u6237\u7aef\u5e93\u3002\u4e0d\u8fc7\u6ca1\u5173\u7cfb\uff0c\u73b0\u5728\u53ef\u4ee5\u901a\u8fc7 UnifiedTTS \u63d0\u4f9b\u7684 API \u6765\u8c03\u7528\u514d\u8d39\u7684 EdgeTTS \u80fd\u529b\u3002\u540c\u65f6\uff0cUnifiedTTS \u8fd8\u652f\u6301 Azure TTS\u3001MiniMax TTS\u3001Elevenlabs TTS \u7b49\u591a\u79cd\u6a21\u578b\uff0c\u901a\u8fc7\u5bf9\u8bf7\u6c42\u63a5\u53e3\u7684\u62bd\u8c61\u5c01\u88c5\uff0c\u7528\u6237\u53ef\u4ee5\u65b9\u4fbf\u5728\u4e0d\u540c\u6a21\u578b\u4e0e\u97f3\u8272\u4e4b\u95f4\u7075\u6d3b\u5207\u6362\u3002<\/p>\n<p>\u4e0b\u9762\u6211\u4eec\u4ee5\u8c03\u7528\u514d\u8d39\u7684EdgeTTS\u4e3a\u76ee\u6807\uff0c\u6784\u5efa\u4e00\u4e2a\u5305\u542b\u6587\u672c\u8f6c\u8bed\u97f3\u529f\u80fd\u7684Spring Boot\u5e94\u7528\u3002<\/p>\n<h2>\u5b9e\u6218<\/h2>\n<h3>1. \u6784\u5efa Spring Boot \u5e94\u7528<\/h3>\n<p>\u901a\u8fc7 <code>start.spring.io<\/code> \u6216\u5176\u4ed6\u6784\u5efa\u57fa\u7840\u7684Spring Boot\u5de5\u7a0b\uff0c\u6839\u636e\u4f60\u6784\u5efa\u5e94\u7528\u7684\u9700\u8981\u589e\u52a0\u4e00\u4e9b\u4f9d\u8d56\uff0c\u6bd4\u5982\u6700\u540e\u7528\u63a5\u53e3\u63d0\u4f9b\u670d\u52a1\u7684\u8bdd\uff0c\u53ef\u4ee5\u52a0\u5165web\u6a21\u5757\uff1a<\/p>\n<pre><code class=\"xml\">&lt;dependencies&gt;\n    &lt;dependency&gt;\n        &lt;groupId&gt;org.springframework.boot&lt;\/groupId&gt;\n        &lt;artifactId&gt;spring-boot-starter-web&lt;\/artifactId&gt;\n    &lt;\/dependency&gt;\n&lt;\/dependencies&gt;<\/code><\/pre>\n<h3>2. \u6ce8\u518c UnifiedTTS\uff0c\u83b7\u53d6 API Key<\/h3>\n<ul>\n<li>\u524d\u5f80 <a href=\"https:\/\/link.segmentfault.com\/?enc=WSoqK4x10Tb0WcDtDAGQfw%3D%3D.X1CTlJNGJ1ErzG3mot1qMBWYDXxQEmSTX4WRhO5oDuc%3D\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">UnifiedTTS<\/a> \u5b98\u7f51\u6ce8\u518c\u8d26\u53f7\uff08\u76f4\u63a5GitHub\u767b\u5f55\u5373\u53ef\uff09<\/li>\n<li>\u4ece\u5de6\u4fa7\u83dc\u5355\u8fdb\u5165\u201cAPI\u5bc6\u94a5\u201d\u9875\u9762\uff0c\u521b\u5efa API Key\uff1b<\/li>\n<\/ul>\n<p><img decoding=\"async\"   referrerpolicy=\"no-referrer\" src=\"https:\/\/segmentfault.com\/img\/remote\/1460000047320207\" alt=\"\u521b\u5efaAPI Key\" title=\"\u521b\u5efaAPI Key\"><\/p>\n<ul>\n<li>\u5b58\u597dAPI Key\uff0c\u540e\u7eed\u9700\u8981\u4f7f\u7528<\/li>\n<\/ul>\n<h3>3. \u96c6\u6210 UnifiedTTS API<\/h3>\n<p>\u4e0b\u9762\u6839\u636eAPI \u6587\u6863\uff1a<a href=\"https:\/\/link.segmentfault.com\/?enc=6L8qjKJSfl5Uu2j5sukkbA%3D%3D.jvJycAyXwGjDVq3Sl%2BPKKCzYZhKCCb5Dj0k8uUgxBYQkkZUglK%2FbudZ9ltrYJ6B0\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">https:\/\/unifiedtts.com\/zh\/api-docs\/tts-sync<\/a>  \u5b9e\u73b0\u4e00\u4e2a\u53ef\u8fd0\u884c\u7684\u53c2\u8003\u5b9e\u73b0\uff0c\u5305\u62ec\u914d\u7f6e\u6587\u4ef6\u3001\u8bf7\u6c42\u6a21\u578b\u3001\u670d\u52a1\u7c7b\u4e0e\u63a7\u5236\u5668\u3002<\/p>\n<h4>3.1 \u914d\u7f6e\u6587\u4ef6\uff08<code>application.properties<\/code>\uff09<\/h4>\n<pre><code class=\"properties\">unified-tts.host=https:\/\/unifiedtts.com\nunified-tts.api-key=your-api-key-here<\/code><\/pre>\n<p>\u8fd9\u91cc<code>unifiedtts.api-key<\/code>\u53c2\u6570\u8bb0\u5f97\u66ff\u6362\u6210\u4e4b\u524d\u521b\u5efa\u7684ApiKey\u3002<\/p>\n<h4>3.2 \u914d\u7f6e\u52a0\u8f7d\u7c7b<\/h4>\n<pre><code class=\"java\">@Data\n@ConfigurationProperties(prefix = &quot;unified-tts&quot;)\npublic class UnifiedTtsProperties {\n\n    private String host;\n    private String apiKey;\n\n}<\/code><\/pre>\n<h4>3.3 \u8bf7\u6c42\u5c01\u88c5\u548c\u54cd\u5e94\u5c01\u88c5<\/h4>\n<pre><code class=\"java\">@Data\n@AllArgsConstructor\n@NoArgsConstructor\npublic class UnifiedTtsRequest {\n    \n    private String model;\n    private String voice;\n    private String text;\n    private Double speed;\n    private Double pitch;\n    private Double volume;\n    private String format;\n\n}\n\n@Data\n@AllArgsConstructor\n@NoArgsConstructor\npublic class UnifiedTtsResponse {\n\n    private boolean success;\n    private String message;\n    private long timestamp;\n    private UnifiedTtsResponseData data;\n\n    @Data\n    @AllArgsConstructor\n    @NoArgsConstructor\n    public static class UnifiedTtsResponseData {\n        @JsonProperty(&quot;request_id&quot;)\n        private String requestId;\n\n        @JsonProperty(&quot;audio_url&quot;)\n        private String audioUrl;\n\n        @JsonProperty(&quot;file_size&quot;)\n        private long fileSize;\n    }\n}<\/code><\/pre>\n<p>UnifiedTTS \u62bd\u8c61\u4e86\u4e0d\u540c\u6a21\u578b\u7684\u8bf7\u6c42\uff0c\u8fd9\u6837\u7528\u6237\u53ef\u4ee5\u7528\u540c\u4e00\u5957\u8bf7\u6c42\u53c2\u6570\u6807\u51c6\u6765\u5b9e\u73b0\u5bf9\u4e0d\u540cTTS\u6a21\u578b\u7684\u8c03\u7528\uff0c\u8fd9\u4e2a\u975e\u5e38\u65b9\u4fbf\u3002\u6240\u4ee5\uff0c\u4e3a\u4e86\u7b80\u5316TTS\u7684\u5ba2\u6237\u7aef\u8c03\u7528\uff0c\u975e\u5e38\u63a8\u8350\u4f7f\u7528 UnifiedTTS\u3002<\/p>\n<h4>3.3 \u670d\u52a1\u5b9e\u73b0\uff08\u8c03\u7528 UnifiedTTS\uff09<\/h4>\n<p>\u4f7f\u7528 Spring Boot\u81ea\u5e26\u7684<code>RestClient<\/code> HTTP\u5ba2\u6237\u7aef\u6765\u5b9e\u73b0<code>UnifiedTTS<\/code>\u7684\u529f\u80fd\u5b9e\u73b0\u7c7b\uff0c\u63d0\u4f9b\u4e24\u4e2a\u5b9e\u73b0\uff1a<\/p>\n<ol>\n<li>\u63a5\u6536\u97f3\u9891\u5b57\u8282\u5e76\u8fd4\u56de\u3002<\/li>\n<\/ol>\n<pre><code class=\"java\">@Service\npublic class UnifiedTtsService {\n\n    private final RestClient restClient;\n    private final UnifiedTtsProperties properties;\n\n    public UnifiedTtsService(RestClient restClient, UnifiedTtsProperties properties) {\n        this.restClient = restClient;\n        this.properties = properties;\n    }\n\n    \/**\n     * \u8c03\u7528 UnifiedTTS \u540c\u6b65 TTS \u63a5\u53e3\uff0c\u8fd4\u56de\u97f3\u9891\u5b57\u8282\u6570\u636e\u3002\n     *\n     * &lt;p&gt;\u8bf7\u6c42\u5934\uff1a\n     * &lt;ul&gt;\n     *   &lt;li&gt;Content-Type: application\/json&lt;\/li&gt;\n     *   &lt;li&gt;X-API-Key: \u6765\u81ea\u914d\u7f6e\u7684 API Key&lt;\/li&gt;\n     *   &lt;li&gt;Accept: \u63a5\u53d7\u4e8c\u8fdb\u5236\u6d41\u6216\u5e38\u89c1 mp3\/mpeg \u97f3\u9891\u7c7b\u578b&lt;\/li&gt;\n     * &lt;\/ul&gt;\n     *\n     * @param request \u6a21\u578b\u3001\u97f3\u8272\u3001\u6587\u672c\u3001\u901f\u5ea6\/\u97f3\u8c03\/\u97f3\u91cf\u3001\u8f93\u51fa\u683c\u5f0f\u7b49\u53c2\u6570\n     * @return \u97f3\u9891\u4e8c\u8fdb\u5236\u5b57\u8282\uff08\u4f8b\u5982 mp3\uff09\n     * @throws IllegalStateException \u5f53\u670d\u52a1\u7aef\u8fd4\u56de\u975e 2xx \u6216\u65e0\u5185\u5bb9\u65f6\u629b\u51fa\n     *\/\n    public byte[] synthesize(UnifiedTtsRequest request) {\n        ResponseEntity&lt;byte[]&gt; response = restClient\n                .post()\n                .uri(&quot;\/api\/v1\/common\/tts-sync&quot;)\n                .contentType(MediaType.APPLICATION_JSON)\n                .accept(MediaType.APPLICATION_OCTET_STREAM, MediaType.valueOf(&quot;audio\/mpeg&quot;), MediaType.valueOf(&quot;audio\/mp3&quot;))\n                .header(&quot;X-API-Key&quot;, properties.getApiKey())\n                .body(request)\n                .retrieve()\n                .toEntity(byte[].class);\n\n        if (response.getStatusCode().is2xxSuccessful() &amp;&amp; response.getBody() != null) {\n            return response.getBody();\n        }\n        throw new IllegalStateException(&quot;UnifiedTTS synthesize failed: &quot; + response.getStatusCode());\n    }\n\n    \/**\n     * \u8c03\u7528\u5408\u6210\u5e76\u5c06\u97f3\u9891\u5199\u5165\u6307\u5b9a\u6587\u4ef6\u3002\n     *\n     * &lt;p&gt;\u82e5\u8f93\u51fa\u8def\u5f84\u7684\u7236\u76ee\u5f55\u4e0d\u5b58\u5728\uff0c\u4f1a\u81ea\u52a8\u521b\u5efa\uff1b\u5931\u8d25\u65f6\u629b\u51fa\u8fd0\u884c\u65f6\u5f02\u5e38\u3002\n     *\n     * @param request TTS \u8bf7\u6c42\u53c2\u6570\n     * @param outputPath \u76ee\u6807\u6587\u4ef6\u8def\u5f84\uff08\u4f8b\u5982 output.mp3\uff09\n     * @return \u5b9e\u9645\u5199\u5165\u7684\u6587\u4ef6\u8def\u5f84\n     *\/\n    public Path synthesizeToFile(UnifiedTtsRequest request, Path outputPath) {\n        byte[] data = synthesize(request);\n        try {\n            if (outputPath.getParent() != null) {\n                Files.createDirectories(outputPath.getParent());\n            }\n            Files.write(outputPath, data);\n            return outputPath;\n        } catch (IOException e) {\n            throw new RuntimeException(&quot;Failed to write TTS output to file: &quot; + outputPath, e);\n        }\n    }\n}<\/code><\/pre>\n<h4>3.4 \u5355\u5143\u6d4b\u8bd5<\/h4>\n<pre><code class=\"java\">@SpringBootTest\nclass UnifiedTtsServiceTest {\n\n    @Autowired\n    private UnifiedTtsService unifiedTtsService;\n\n    @Test\n    void testRealSynthesizeAndDownloadToFile() throws Exception {\n        UnifiedTtsRequest req = new UnifiedTtsRequest(\n            &quot;edge-tts&quot;,\n            &quot;en-US-JennyNeural&quot;,\n            &quot;Hello, this is a test of text to speech synthesis.&quot;,\n            1.0,\n            1.0,\n            1.0,\n            &quot;mp3&quot;\n        );\n\n        \/\/ \u8c03\u7528\u771f\u5b9e\u63a5\u53e3\uff0c\u65ad\u8a00\u8fd4\u56de\u7ed3\u6784\n        UnifiedTtsResponse resp = unifiedTtsService.synthesize(req);\n        assertNotNull(resp);\n        assertTrue(resp.isSuccess(), &quot;Response should be success&quot;);\n        assertNotNull(resp.getData(), &quot;Response data should not be null&quot;);\n        assertNotNull(resp.getData().getAudioUrl(), &quot;audio_url should be present&quot;);\n\n        \/\/ \u5728\u5f53\u524d\u5de5\u7a0b\u76ee\u5f55\u4e0b\u751f\u6210\u6d4b\u8bd5\u7ed3\u679c\u76ee\u5f55\u5e76\u5199\u5165\u6587\u4ef6\n        Path projectDir = Paths.get(System.getProperty(&quot;user.dir&quot;));\n        Path resultDir = projectDir.resolve(&quot;test-result&quot;);\n        Files.createDirectories(resultDir);\n        Path out = resultDir.resolve(System.currentTimeMillis() + &quot;.mp3&quot;);\n        Path written = unifiedTtsService.synthesizeToFile(req, out);\n        System.out.println(&quot;UnifiedTTS test output: &quot; + written.toAbsolutePath());\n        assertTrue(Files.exists(written), &quot;Output file should exist&quot;);\n        assertTrue(Files.size(written) &gt; 0, &quot;Output file size should be &gt; 0&quot;);\n    }\n}<\/code><\/pre>\n<h3>4. \u8fd0\u884c\u4e0e\u9a8c\u8bc1<\/h3>\n<p>\u6267\u884c\u5355\u5143\u6d4b\u8bd5\u4e4b\u540e\uff0c\u53ef\u4ee5\u5728\u5de5\u7a0b\u76ee\u5f55<code>test-result<\/code>\u4e0b\u627e\u5230\u751f\u6210\u7684\u97f3\u9891\u6587\u4ef6\uff1a<\/p>\n<p><img decoding=\"async\"   referrerpolicy=\"no-referrer\" src=\"https:\/\/segmentfault.com\/img\/remote\/1460000047320208\" alt=\"\" title=\"\" loading=\"lazy\"><\/p>\n<h3>5. \u5e38\u7528\u53c2\u6570\u4e0e\u97f3\u8272\u9009\u62e9<\/h3>\n<p>\u76ee\u524d\u652f\u6301\u7684\u5e38\u7528\u53c2\u6570\u5982\u4e0b\u56fe\u6240\u793a\uff1a<\/p>\n<p><img decoding=\"async\"   referrerpolicy=\"no-referrer\" src=\"https:\/\/segmentfault.com\/img\/remote\/1460000047320209\" alt=\"\" title=\"\" loading=\"lazy\"><\/p>\n<blockquote><p>\u5bf9\u4e8emodel\u548cvoice\u53c2\u6570\u53ef\u4ee5\u56e0\u4e3a\u5185\u5bb9\u8f83\u591a\uff0c\u53ef\u4ee5\u524d\u5f80<a href=\"https:\/\/link.segmentfault.com\/?enc=CKXtaxcXv%2BnghTmhDwY2Aw%3D%3D.LYhyCHTmlAm1tgbjSWBPhlAyYEhCna5jDzHCiuel%2FDOvJVzmN%2Bu6CVmqhQHrX%2B0X\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">API\u6587\u6863<\/a>\u67e5\u770b\u3002<\/p><\/blockquote>\n<h2>\u5c0f\u7ed3<\/h2>\n<p>\u672c\u6587\u5c55\u793a\u4e86\u5982\u4f55\u5728 Spring Boot \u4e2d\u96c6\u6210 UnifiedTTS \u7684 EdgeTTS \u80fd\u529b\uff0c\u5b9e\u73b0\u6587\u672c\u8f6c\u8bed\u97f3\u5e76\u8f93\u51fa\u4e3a <code>mp3<\/code>\u3002UnifiedTTS \u901a\u8fc7\u7edf\u4e00\u7684 API \u5c4f\u853d\u4e86\u4e0d\u540c TTS \u6a21\u578b\u7684\u5dee\u5f02\uff0c\u4f7f\u4f60\u65e0\u9700\u7ef4\u62a4\u591a\u4e2a SDK\uff0c\u5373\u53ef\u5728\u6210\u672c\u4e0e\u6548\u679c\u4e4b\u95f4\u81ea\u7531\u5207\u6362\u3002\u6839\u636e\u4e1a\u52a1\u9700\u6c42\uff0c\u4f60\u53ef\u4ee5\u8fdb\u4e00\u6b65\u5b8c\u5584\u5f02\u5e38\u5904\u7406\u3001\u7f13\u5b58\u4e0e\u5e76\u53d1\u63a7\u5236\uff0c\u5b9e\u73b0\u66f4\u53ef\u9760\u7684\u751f\u4ea7\u7ea7 TTS \u670d\u52a1\u3002<\/p>\n<p>\u672c\u6587\u6837\u4f8b\u5de5\u7a0b\uff1a<a href=\"https:\/\/link.segmentfault.com\/?enc=smGVzl%2BZi0r3qi2ehOnnVw%3D%3D.KiF9wTcV9ObjJhnVqFab9Zhhs4pThb1aZAWGfT1xJubZFIVyCB0nuZYowYkWp%2Brz\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">https:\/\/github.com\/dyc87112\/unified-tts-example<\/a><\/p>\n<p><a href=\"https:\/\/segmentfault.com\/a\/1190000047320205\">https:\/\/segmentfault.com\/a\/1190000047320205<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>\u5728\u9700\u8981\u6587\u672c\u8f6c\u8bed\u97f3\uff08TTS\uff09\u7684\u5e94\u7528\u573a\u666f\u4e2d\uff08\u5982\u8bed\u97f3\u52a9\u624b\u3001\u8bed\u97f3\u901a\u77e5\u3001\u5185\u5bb9\u64ad\u62a5\u7b49\uff09\uff0cJava\u751f\u6001\u7f3a\u5c11\u7c7b\u4f3cPython\u751f [&hellip;]<\/p>\n","protected":false},"author":7,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[4],"tags":[],"class_list":["post-10021033","post","type-post","status-publish","format-standard","hentry","category-share"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts\/10021033","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=10021033"}],"version-history":[{"count":0,"href":"https:\/\/itmax.cn\/index.php?rest_route=\/wp\/v2\/posts\/10021033\/revisions"}],"wp:attachment":[{"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=10021033"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=10021033"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/itmax.cn\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=10021033"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}