├── .gitignore
├── .mvn
└── wrapper
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
└── main
├── java
└── com
│ └── fjut
│ └── noveltts
│ ├── NovelTtsApplication.java
│ ├── config
│ └── MvcConfigurer.java
│ ├── controller
│ └── HandleController.java
│ ├── entity
│ ├── ArticleData.java
│ ├── SentenceInfo.java
│ ├── SentenceVo.java
│ └── SpeakerInfo.java
│ └── utils
│ ├── HandleAudioUtil.java
│ ├── HandleModelConfigUtil.java
│ ├── HandleTextUtil.java
│ ├── ProgramUtil.java
│ └── Result.java
└── resources
├── application.properties
├── config.txt
├── static
├── assets
│ ├── index-BijUbyZy.css
│ └── index-DSxEuOva.js
└── favicon.ico
└── templates
└── index.html
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | target/
3 | !.mvn/wrapper/maven-wrapper.jar
4 | !**/src/main/**/target/
5 | !**/src/test/**/target/
6 |
7 | ### STS ###
8 | .apt_generated
9 | .classpath
10 | .factorypath
11 | .project
12 | .settings
13 | .springBeans
14 | .sts4-cache
15 |
16 | ### IntelliJ IDEA ###
17 | .idea
18 | *.iws
19 | *.iml
20 | *.ipr
21 |
22 | ### NetBeans ###
23 | /nbproject/private/
24 | /nbbuild/
25 | /dist/
26 | /nbdist/
27 | /.nb-gradle/
28 | build/
29 | !**/src/main/**/build/
30 | !**/src/test/**/build/
31 |
32 | ### VS Code ###
33 | .vscode/
34 |
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qumingzhentmdnan/NovelTTS/62e665ece1c5eda72c4cc1d31a791b0d95541067/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 使用演示视频:https://www.bilibili.com/video/BV1Kx421Q7kS/?spm_id_from=333.999.0.0
2 |
--------------------------------------------------------------------------------
/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # https://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Apache Maven Wrapper startup batch script, version 3.2.0
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
31 | # e.g. to debug Maven itself, use
32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34 | # ----------------------------------------------------------------------------
35 |
36 | if [ -z "$MAVEN_SKIP_RC" ] ; then
37 |
38 | if [ -f /usr/local/etc/mavenrc ] ; then
39 | . /usr/local/etc/mavenrc
40 | fi
41 |
42 | if [ -f /etc/mavenrc ] ; then
43 | . /etc/mavenrc
44 | fi
45 |
46 | if [ -f "$HOME/.mavenrc" ] ; then
47 | . "$HOME/.mavenrc"
48 | fi
49 |
50 | fi
51 |
52 | # OS specific support. $var _must_ be set to either true or false.
53 | cygwin=false;
54 | darwin=false;
55 | mingw=false
56 | case "$(uname)" in
57 | CYGWIN*) cygwin=true ;;
58 | MINGW*) mingw=true;;
59 | Darwin*) darwin=true
60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
62 | if [ -z "$JAVA_HOME" ]; then
63 | if [ -x "/usr/libexec/java_home" ]; then
64 | JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
65 | else
66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
67 | fi
68 | fi
69 | ;;
70 | esac
71 |
72 | if [ -z "$JAVA_HOME" ] ; then
73 | if [ -r /etc/gentoo-release ] ; then
74 | JAVA_HOME=$(java-config --jre-home)
75 | fi
76 | fi
77 |
78 | # For Cygwin, ensure paths are in UNIX format before anything is touched
79 | if $cygwin ; then
80 | [ -n "$JAVA_HOME" ] &&
81 | JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
82 | [ -n "$CLASSPATH" ] &&
83 | CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
84 | fi
85 |
86 | # For Mingw, ensure paths are in UNIX format before anything is touched
87 | if $mingw ; then
88 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
89 | JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
90 | fi
91 |
92 | if [ -z "$JAVA_HOME" ]; then
93 | javaExecutable="$(which javac)"
94 | if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
95 | # readlink(1) is not available as standard on Solaris 10.
96 | readLink=$(which readlink)
97 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
98 | if $darwin ; then
99 | javaHome="$(dirname "\"$javaExecutable\"")"
100 | javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
101 | else
102 | javaExecutable="$(readlink -f "\"$javaExecutable\"")"
103 | fi
104 | javaHome="$(dirname "\"$javaExecutable\"")"
105 | javaHome=$(expr "$javaHome" : '\(.*\)/bin')
106 | JAVA_HOME="$javaHome"
107 | export JAVA_HOME
108 | fi
109 | fi
110 | fi
111 |
112 | if [ -z "$JAVACMD" ] ; then
113 | if [ -n "$JAVA_HOME" ] ; then
114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
115 | # IBM's JDK on AIX uses strange locations for the executables
116 | JAVACMD="$JAVA_HOME/jre/sh/java"
117 | else
118 | JAVACMD="$JAVA_HOME/bin/java"
119 | fi
120 | else
121 | JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
122 | fi
123 | fi
124 |
125 | if [ ! -x "$JAVACMD" ] ; then
126 | echo "Error: JAVA_HOME is not defined correctly." >&2
127 | echo " We cannot execute $JAVACMD" >&2
128 | exit 1
129 | fi
130 |
131 | if [ -z "$JAVA_HOME" ] ; then
132 | echo "Warning: JAVA_HOME environment variable is not set."
133 | fi
134 |
135 | # traverses directory structure from process work directory to filesystem root
136 | # first directory with .mvn subdirectory is considered project base directory
137 | find_maven_basedir() {
138 | if [ -z "$1" ]
139 | then
140 | echo "Path not specified to find_maven_basedir"
141 | return 1
142 | fi
143 |
144 | basedir="$1"
145 | wdir="$1"
146 | while [ "$wdir" != '/' ] ; do
147 | if [ -d "$wdir"/.mvn ] ; then
148 | basedir=$wdir
149 | break
150 | fi
151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc)
152 | if [ -d "${wdir}" ]; then
153 | wdir=$(cd "$wdir/.." || exit 1; pwd)
154 | fi
155 | # end of workaround
156 | done
157 | printf '%s' "$(cd "$basedir" || exit 1; pwd)"
158 | }
159 |
160 | # concatenates all lines of a file
161 | concat_lines() {
162 | if [ -f "$1" ]; then
163 | # Remove \r in case we run on Windows within Git Bash
164 | # and check out the repository with auto CRLF management
165 | # enabled. Otherwise, we may read lines that are delimited with
166 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
167 | # splitting rules.
168 | tr -s '\r\n' ' ' < "$1"
169 | fi
170 | }
171 |
172 | log() {
173 | if [ "$MVNW_VERBOSE" = true ]; then
174 | printf '%s\n' "$1"
175 | fi
176 | }
177 |
178 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
179 | if [ -z "$BASE_DIR" ]; then
180 | exit 1;
181 | fi
182 |
183 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
184 | log "$MAVEN_PROJECTBASEDIR"
185 |
186 | ##########################################################################################
187 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
188 | # This allows using the maven wrapper in projects that prohibit checking in binary data.
189 | ##########################################################################################
190 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
191 | if [ -r "$wrapperJarPath" ]; then
192 | log "Found $wrapperJarPath"
193 | else
194 | log "Couldn't find $wrapperJarPath, downloading it ..."
195 |
196 | if [ -n "$MVNW_REPOURL" ]; then
197 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
198 | else
199 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
200 | fi
201 | while IFS="=" read -r key value; do
202 | # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
203 | safeValue=$(echo "$value" | tr -d '\r')
204 | case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
205 | esac
206 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
207 | log "Downloading from: $wrapperUrl"
208 |
209 | if $cygwin; then
210 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
211 | fi
212 |
213 | if command -v wget > /dev/null; then
214 | log "Found wget ... using wget"
215 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
216 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
217 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
218 | else
219 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
220 | fi
221 | elif command -v curl > /dev/null; then
222 | log "Found curl ... using curl"
223 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
226 | else
227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
228 | fi
229 | else
230 | log "Falling back to using Java to download"
231 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
232 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
233 | # For Cygwin, switch paths to Windows format before running javac
234 | if $cygwin; then
235 | javaSource=$(cygpath --path --windows "$javaSource")
236 | javaClass=$(cygpath --path --windows "$javaClass")
237 | fi
238 | if [ -e "$javaSource" ]; then
239 | if [ ! -e "$javaClass" ]; then
240 | log " - Compiling MavenWrapperDownloader.java ..."
241 | ("$JAVA_HOME/bin/javac" "$javaSource")
242 | fi
243 | if [ -e "$javaClass" ]; then
244 | log " - Running MavenWrapperDownloader.java ..."
245 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
246 | fi
247 | fi
248 | fi
249 | fi
250 | ##########################################################################################
251 | # End of extension
252 | ##########################################################################################
253 |
254 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file
255 | wrapperSha256Sum=""
256 | while IFS="=" read -r key value; do
257 | case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
258 | esac
259 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
260 | if [ -n "$wrapperSha256Sum" ]; then
261 | wrapperSha256Result=false
262 | if command -v sha256sum > /dev/null; then
263 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
264 | wrapperSha256Result=true
265 | fi
266 | elif command -v shasum > /dev/null; then
267 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
268 | wrapperSha256Result=true
269 | fi
270 | else
271 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
272 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
273 | exit 1
274 | fi
275 | if [ $wrapperSha256Result = false ]; then
276 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
277 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
278 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
279 | exit 1
280 | fi
281 | fi
282 |
283 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
284 |
285 | # For Cygwin, switch paths to Windows format before running java
286 | if $cygwin; then
287 | [ -n "$JAVA_HOME" ] &&
288 | JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
289 | [ -n "$CLASSPATH" ] &&
290 | CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
291 | [ -n "$MAVEN_PROJECTBASEDIR" ] &&
292 | MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
293 | fi
294 |
295 | # Provide a "standardized" way to retrieve the CLI args that will
296 | # work with both Windows and non-Windows executions.
297 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
298 | export MAVEN_CMD_LINE_ARGS
299 |
300 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
301 |
302 | # shellcheck disable=SC2086 # safe args
303 | exec "$JAVACMD" \
304 | $MAVEN_OPTS \
305 | $MAVEN_DEBUG_OPTS \
306 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
307 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
308 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
309 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | @REM ----------------------------------------------------------------------------
2 | @REM Licensed to the Apache Software Foundation (ASF) under one
3 | @REM or more contributor license agreements. See the NOTICE file
4 | @REM distributed with this work for additional information
5 | @REM regarding copyright ownership. The ASF licenses this file
6 | @REM to you under the Apache License, Version 2.0 (the
7 | @REM "License"); you may not use this file except in compliance
8 | @REM with the License. You may obtain a copy of the License at
9 | @REM
10 | @REM https://www.apache.org/licenses/LICENSE-2.0
11 | @REM
12 | @REM Unless required by applicable law or agreed to in writing,
13 | @REM software distributed under the License is distributed on an
14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | @REM KIND, either express or implied. See the License for the
16 | @REM specific language governing permissions and limitations
17 | @REM under the License.
18 | @REM ----------------------------------------------------------------------------
19 |
20 | @REM ----------------------------------------------------------------------------
21 | @REM Apache Maven Wrapper startup batch script, version 3.2.0
22 | @REM
23 | @REM Required ENV vars:
24 | @REM JAVA_HOME - location of a JDK home dir
25 | @REM
26 | @REM Optional ENV vars
27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
30 | @REM e.g. to debug Maven itself, use
31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
33 | @REM ----------------------------------------------------------------------------
34 |
35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
36 | @echo off
37 | @REM set title of command window
38 | title %0
39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
41 |
42 | @REM set %HOME% to equivalent of $HOME
43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
44 |
45 | @REM Execute a user defined script before this one
46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
50 | :skipRcPre
51 |
52 | @setlocal
53 |
54 | set ERROR_CODE=0
55 |
56 | @REM To isolate internal variables from possible post scripts, we use another setlocal
57 | @setlocal
58 |
59 | @REM ==== START VALIDATION ====
60 | if not "%JAVA_HOME%" == "" goto OkJHome
61 |
62 | echo.
63 | echo Error: JAVA_HOME not found in your environment. >&2
64 | echo Please set the JAVA_HOME variable in your environment to match the >&2
65 | echo location of your Java installation. >&2
66 | echo.
67 | goto error
68 |
69 | :OkJHome
70 | if exist "%JAVA_HOME%\bin\java.exe" goto init
71 |
72 | echo.
73 | echo Error: JAVA_HOME is set to an invalid directory. >&2
74 | echo JAVA_HOME = "%JAVA_HOME%" >&2
75 | echo Please set the JAVA_HOME variable in your environment to match the >&2
76 | echo location of your Java installation. >&2
77 | echo.
78 | goto error
79 |
80 | @REM ==== END VALIDATION ====
81 |
82 | :init
83 |
84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
85 | @REM Fallback to current working directory if not found.
86 |
87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
89 |
90 | set EXEC_DIR=%CD%
91 | set WDIR=%EXEC_DIR%
92 | :findBaseDir
93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
94 | cd ..
95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
96 | set WDIR=%CD%
97 | goto findBaseDir
98 |
99 | :baseDirFound
100 | set MAVEN_PROJECTBASEDIR=%WDIR%
101 | cd "%EXEC_DIR%"
102 | goto endDetectBaseDir
103 |
104 | :baseDirNotFound
105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
106 | cd "%EXEC_DIR%"
107 |
108 | :endDetectBaseDir
109 |
110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
111 |
112 | @setlocal EnableExtensions EnableDelayedExpansion
113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
115 |
116 | :endReadAdditionalConfig
117 |
118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
121 |
122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
123 |
124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
126 | )
127 |
128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data.
130 | if exist %WRAPPER_JAR% (
131 | if "%MVNW_VERBOSE%" == "true" (
132 | echo Found %WRAPPER_JAR%
133 | )
134 | ) else (
135 | if not "%MVNW_REPOURL%" == "" (
136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
137 | )
138 | if "%MVNW_VERBOSE%" == "true" (
139 | echo Couldn't find %WRAPPER_JAR%, downloading it ...
140 | echo Downloading from: %WRAPPER_URL%
141 | )
142 |
143 | powershell -Command "&{"^
144 | "$webclient = new-object System.Net.WebClient;"^
145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
147 | "}"^
148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
149 | "}"
150 | if "%MVNW_VERBOSE%" == "true" (
151 | echo Finished downloading %WRAPPER_JAR%
152 | )
153 | )
154 | @REM End of extension
155 |
156 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
157 | SET WRAPPER_SHA_256_SUM=""
158 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
159 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
160 | )
161 | IF NOT %WRAPPER_SHA_256_SUM%=="" (
162 | powershell -Command "&{"^
163 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
164 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
165 | " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
166 | " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
167 | " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
168 | " exit 1;"^
169 | "}"^
170 | "}"
171 | if ERRORLEVEL 1 goto error
172 | )
173 |
174 | @REM Provide a "standardized" way to retrieve the CLI args that will
175 | @REM work with both Windows and non-Windows executions.
176 | set MAVEN_CMD_LINE_ARGS=%*
177 |
178 | %MAVEN_JAVA_EXE% ^
179 | %JVM_CONFIG_MAVEN_PROPS% ^
180 | %MAVEN_OPTS% ^
181 | %MAVEN_DEBUG_OPTS% ^
182 | -classpath %WRAPPER_JAR% ^
183 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
184 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
185 | if ERRORLEVEL 1 goto error
186 | goto end
187 |
188 | :error
189 | set ERROR_CODE=1
190 |
191 | :end
192 | @endlocal & set ERROR_CODE=%ERROR_CODE%
193 |
194 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
195 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
196 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
197 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
198 | :skipRcPost
199 |
200 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
201 | if "%MAVEN_BATCH_PAUSE%"=="on" pause
202 |
203 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
204 |
205 | cmd /C exit /B %ERROR_CODE%
206 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 3.2.3
9 |
10 |
11 | com.fjut
12 | NovelTTS
13 | 0.0.1-SNAPSHOT
14 | NovelTTS
15 | NovelTTS
16 |
17 | 21
18 |
19 |
20 |
21 |
22 | org.springframework.boot
23 | spring-boot-starter
24 |
25 |
26 |
27 | org.springframework.boot
28 | spring-boot-starter-test
29 | test
30 |
31 |
32 |
33 |
34 | com.fasterxml.jackson.core
35 | jackson-databind
36 | 2.15.3
37 |
38 |
39 |
40 | org.springframework.boot
41 | spring-boot-starter-thymeleaf
42 |
43 |
44 |
45 |
46 | org.springframework.boot
47 | spring-boot-starter-web
48 |
49 |
50 |
51 | org.springframework.boot
52 | spring-boot-devtools
53 | true
54 |
55 |
56 |
57 |
58 |
59 |
60 | org.springframework.boot
61 | spring-boot-maven-plugin
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/src/main/java/com/fjut/noveltts/NovelTtsApplication.java:
--------------------------------------------------------------------------------
1 | package com.fjut.noveltts;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class NovelTtsApplication {
8 | public static void main(String[] args) {
9 | SpringApplication.run(NovelTtsApplication.class, args);
10 | System.err.println("Server started: http://localhost:8000/handle/index");
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/fjut/noveltts/config/MvcConfigurer.java:
--------------------------------------------------------------------------------
1 | package com.fjut.noveltts.config;
2 |
3 | import org.aopalliance.intercept.Interceptor;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.springframework.web.servlet.config.annotation.CorsRegistry;
7 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
9 |
10 | @Configuration
11 | public class MvcConfigurer implements WebMvcConfigurer {
12 | @Override
13 | public void addCorsMappings(CorsRegistry registry) {
14 | registry.addMapping("/**")
15 | .allowedOriginPatterns("*")
16 | .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
17 | .allowedHeaders("*")
18 | .allowCredentials(true);
19 | }
20 | }
--------------------------------------------------------------------------------
/src/main/java/com/fjut/noveltts/controller/HandleController.java:
--------------------------------------------------------------------------------
1 | package com.fjut.noveltts.controller;
2 |
3 | import com.fasterxml.jackson.core.type.TypeReference;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import com.fjut.noveltts.entity.ArticleData;
6 | import com.fjut.noveltts.entity.SentenceInfo;
7 | import com.fjut.noveltts.entity.SentenceVo;
8 | import com.fjut.noveltts.entity.SpeakerInfo;
9 | import com.fjut.noveltts.utils.*;
10 | import org.springframework.stereotype.Controller;
11 | import org.springframework.web.bind.annotation.*;
12 |
13 | import java.io.File;
14 | import java.io.IOException;
15 | import java.nio.file.Files;
16 | import java.nio.file.Paths;
17 | import java.util.*;
18 | import java.util.regex.Matcher;
19 | import java.util.regex.Pattern;
20 |
21 | @Controller
22 | @RequestMapping("/handle")
23 | @CrossOrigin
24 | public class HandleController {
25 |
26 | @GetMapping("/index")
27 | public String toIndex(){
28 | return "index";
29 | }
30 |
31 | //将小说角色和朗读声音角色进行绑定添加到配置文件中
32 | @PutMapping("/addBundleConfig")
33 | @ResponseBody
34 | public Result addCharacter(@RequestBody SpeakerInfo[] speakerInfo){
35 | HashMap map = new HashMap<>();
36 | for (SpeakerInfo info : speakerInfo) {
37 | if(info.getSpeaker().equals("默认旁白角色")||info.getSpeaker().equals("默认对话角色"))
38 | continue;
39 | map.put(info.getSpeaker(),info.getCharacter()+"@&"+info.getEmotion());
40 | }
41 | ProgramUtil.addConfig(map);
42 | return Result.ok().message("数据绑定成功");
43 | }
44 |
45 | @ResponseBody
46 | //得到所有配音角色和朗读声音角色的绑定
47 | @GetMapping("/getBundleConfig")
48 | public Result getBundleConfig(){
49 | ArrayList speakerInfos = new ArrayList<>();
50 | Map map = ProgramUtil.getconfig();
51 | if(map==null)
52 | return null;
53 | for (Map.Entry next : map.entrySet()) {
54 | if(next.getValue().contains("@&")){
55 | String[] split = next.getValue().split("@&");
56 | speakerInfos.add(new SpeakerInfo(next.getKey(), split[1], split[0]));
57 | }
58 | }
59 | speakerInfos.add(new SpeakerInfo("默认对话角色",ProgramUtil.getConfigByKey("defaultDialogueEmotion"),ProgramUtil.getConfigByKey("defaultDialogueCharacter")));
60 | speakerInfos.add(new SpeakerInfo("默认旁白角色",ProgramUtil.getConfigByKey("defaultNarrationEmotion"),ProgramUtil.getConfigByKey("defaultNarrationCharacter")));
61 | return Result.ok().data("speakerInfos",speakerInfos);
62 | }
63 |
64 | @ResponseBody
65 | //添加配置文件
66 | @PutMapping("/addConfig")
67 | public Result addCharacter(@RequestBody HashMap configMap){
68 | ProgramUtil.addConfig(configMap);
69 | return Result.ok().message("配置文件添加成功");
70 | }
71 |
72 | @ResponseBody
73 | //得到所有配音角色
74 | @GetMapping("/getAllCharacter")
75 | public Result getAllCharacter(){
76 | Map> characterList = ProgramUtil.getCharacterList();
77 | Set strings = characterList.keySet();
78 | return Result.ok().data("characterList",strings);
79 | }
80 |
81 | @ResponseBody
82 | //根据配音角色得到所有配音角色所有的感情
83 | @GetMapping("/getEmotionByCharacter")
84 | public Result getEmotionByCharacter(String character){
85 | Map> characterList = ProgramUtil.getCharacterList();
86 | List strings = characterList.get(character);
87 | return Result.ok().data("emotionList",strings);
88 | }
89 |
90 | @ResponseBody
91 | //查询配置文件中的所有配音角色及所有情感
92 | @GetMapping("/getAllCharacterAndEmotion")
93 | public Result getCharacterInfo(){
94 | Map> characterList = ProgramUtil.getCharacterList();
95 | return Result.ok().data("characterInConfig",characterList);
96 | }
97 |
98 | @ResponseBody
99 | @DeleteMapping("/deleteConfig")
100 | public Result deleteConfig(String speaker){
101 | ProgramUtil.deleteConfig(speaker);
102 | return Result.ok().message("删除成功");
103 | }
104 |
105 | @ResponseBody
106 | //转换模型配置文件
107 | @GetMapping("/handleModelConfig")
108 | public Result handleModelConfig(String modelPath){
109 | if(!Files.isDirectory(Paths.get(modelPath)))
110 | return Result.error().message("未配置文件夹路径或错误的文件夹路径");
111 | HandleModelConfigUtil.setConfigFile(modelPath);
112 | return Result.ok().message("模型配置文件转换成功");
113 | }
114 |
115 |
116 | @ResponseBody
117 | //进行小说文件切分,以章切分为一个文件
118 | @GetMapping("/splitArticle")
119 | public Result splitArticle(String novelPath){
120 | //写入配置文件
121 | ProgramUtil.addConfig("novelPath",novelPath.replace(".txt",""));
122 | if(!novelPath.endsWith(".txt"))
123 | return Result.error().message("错误的文件格式,小说需为txt类型");
124 | if(!Files.isRegularFile(Paths.get(novelPath)))
125 | return Result.error().message("小说未配置文件路径或错误的文件路径");
126 | HandleTextUtil.splitArticle(novelPath);
127 | return Result.ok().message("小说切分成功,请前往"+novelPath.replace(".txt","")+"查看");
128 | }
129 |
130 | @ResponseBody
131 | //得到所有切分后的文件名
132 | @GetMapping("/getArticleList")
133 | public Result getArticleList(){
134 | String novelPath = ProgramUtil.getConfigByKey("novelPath");
135 | if(novelPath==null)
136 | return Result.error().message("未配置小说文件路径");
137 | String[] list = new File(novelPath).list();
138 | if(list==null)
139 | return Result.error().message("未找到切分后的小说文件");
140 | Arrays.sort(list, (s1, s2) -> {
141 | Pattern p = Pattern.compile("^\\d+");
142 | Matcher m1 = p.matcher(s1);
143 | Matcher m2 = p.matcher(s2);
144 | if (m1.find() && m2.find()) {
145 | return Integer.compare(Integer.parseInt(m1.group()), Integer.parseInt(m2.group()));
146 | }
147 | return s1.compareTo(s2);
148 | });
149 | return Result.ok().data("getArticleList",list);
150 | }
151 |
152 | @ResponseBody
153 | //返回一章小说切分和说话人,并且预测结果
154 | @GetMapping("/getHandledArticle")
155 | public Result getHandledArticle(String articleName){
156 | String novelPath = ProgramUtil.getConfigByKey("novelPath");
157 | if(novelPath==null)
158 | return Result.error().message("未配置小说文件路径");
159 | ArrayList sentenceInfos = HandleTextUtil.formatArticle(novelPath.replace(".txt", "") + "/" + articleName);
160 | if(sentenceInfos==null)
161 | return Result.error();
162 | return Result.ok().data("sentenceInfos",sentenceInfos);
163 | }
164 |
165 | @ResponseBody
166 | //接受调整成功的小说内容,对其进行转语音
167 | //测试
168 | @PostMapping("/saveFormatArticle")
169 | public Result saveFormatArticle(@RequestBody ArticleData articleData){
170 | if(articleData.getNovelName()==null)
171 | return Result.error().message("小说名不能为空");
172 | String novelPath = ProgramUtil.getConfigByKey("novelPath")+"format/";
173 | new File(novelPath).mkdirs();
174 | HandleTextUtil.saveFormatArticle(novelPath+articleData.getNovelName(),articleData.getSentenceInfo());
175 | return Result.ok();
176 | }
177 |
178 | @ResponseBody
179 | @GetMapping("/novelTTS")
180 | public Result novelTTS(){
181 | try {
182 | String novelPath = ProgramUtil.getConfigByKey("novelPath");
183 | ArrayList sentenceInfo = null;
184 | File[] files = new File(novelPath+"format/").listFiles();
185 | for (File file : files) {
186 | // 创建 ObjectMapper 对象
187 | ObjectMapper mapper = new ObjectMapper();
188 | // 读取 JSON 文件并转换为 ArrayList 对象
189 | sentenceInfo = mapper.readValue(file, new TypeReference>(){});
190 | HandleAudioUtil.textToAudio(sentenceInfo,novelPath);
191 | boolean delete = file.delete();
192 | if(delete)
193 | System.out.println(file+"已合成,删除文件成功");
194 | }
195 | } catch (IOException e) {
196 | throw new RuntimeException(e);
197 | }
198 | return Result.ok();
199 | }
200 | }
--------------------------------------------------------------------------------
/src/main/java/com/fjut/noveltts/entity/ArticleData.java:
--------------------------------------------------------------------------------
1 | package com.fjut.noveltts.entity;
2 |
3 | import java.util.ArrayList;
4 |
5 | public class ArticleData {
6 | private ArrayList sentenceInfo;
7 | private String novelName;
8 |
9 | public ArrayList getSentenceInfo() {
10 | return sentenceInfo;
11 | }
12 |
13 | public void setSentenceInfo(ArrayList sentenceInfo) {
14 | this.sentenceInfo = sentenceInfo;
15 | }
16 |
17 | public String getNovelName() {
18 | return novelName;
19 | }
20 |
21 | public void setNovelName(String novelName) {
22 | this.novelName = novelName;
23 | }
24 |
25 | // getters and setters
26 | }
--------------------------------------------------------------------------------
/src/main/java/com/fjut/noveltts/entity/SentenceInfo.java:
--------------------------------------------------------------------------------
1 | package com.fjut.noveltts.entity;
2 |
3 | public class SentenceInfo {
4 | //是否是对话
5 | private boolean dialogue;
6 | //批处理大小
7 | private Integer batchSize=1;
8 | //句子
9 | private String sentence;
10 | //说话者
11 | private String speaker;
12 | //配音角色
13 | private String character;
14 | //情感
15 | private String emotion="default";
16 |
17 | private double speed=1.0;
18 |
19 | public String getEmotion() {
20 | return emotion;
21 | }
22 |
23 | public void setEmotion(String emotion) {
24 | this.emotion = emotion;
25 | }
26 |
27 | public SentenceInfo(boolean isDialogue, String sentence) {
28 | this.dialogue = isDialogue;
29 | this.sentence = sentence;
30 | }
31 |
32 | public boolean isDialogue() {
33 | return dialogue;
34 | }
35 |
36 | public void setDialogue(boolean dialogue) {
37 | this.dialogue = dialogue;
38 | }
39 |
40 | public Integer getBatchSize() {
41 | return batchSize;
42 | }
43 |
44 | public void setBatchSize(Integer batchSize) {
45 | this.batchSize = batchSize;
46 | }
47 |
48 | public double getSpeed() {
49 | return speed;
50 | }
51 |
52 | public void setSpeed(double speed) {
53 | this.speed = speed;
54 | }
55 |
56 | public String getSentence() {
57 | return sentence;
58 | }
59 |
60 | public void setSentence(String sentence) {
61 | this.sentence = sentence;
62 | }
63 |
64 | public String getSpeaker() {
65 | return speaker;
66 | }
67 |
68 | public void setSpeaker(String speaker) {
69 | this.speaker = speaker;
70 | }
71 |
72 | public SentenceInfo() {
73 | }
74 |
75 | @Override
76 | public String toString() {
77 | return "SentenceInfo{" +
78 | "isDialogue=" + dialogue +
79 | ", batchSize=" + batchSize +
80 | ", sentence='" + sentence + '\'' +
81 | ", speaker='" + speaker + '\'' +
82 | ", character='" + character + '\'' +
83 | ", emotion='" + emotion + '\'' +
84 | ", speed=" + speed +
85 | '}';
86 | }
87 |
88 | public String getCharacter() {
89 | return character;
90 | }
91 |
92 | public void setCharacter(String character) {
93 | this.character = character;
94 | }
95 | }
--------------------------------------------------------------------------------
/src/main/java/com/fjut/noveltts/entity/SentenceVo.java:
--------------------------------------------------------------------------------
1 | package com.fjut.noveltts.entity;
2 |
3 | public class SentenceVo {
4 | //句子
5 | private String sentence;
6 | //说话者
7 | private String speaker;
8 |
9 | public SentenceVo(String sentence, String speaker) {
10 | this.sentence = sentence;
11 | this.speaker = speaker;
12 | }
13 |
14 | public SentenceVo() {
15 | }
16 | }
--------------------------------------------------------------------------------
/src/main/java/com/fjut/noveltts/entity/SpeakerInfo.java:
--------------------------------------------------------------------------------
1 | package com.fjut.noveltts.entity;
2 |
3 | public class SpeakerInfo {
4 | String speaker;
5 | String emotion;
6 | String character;
7 |
8 | @Override
9 | public String toString() {
10 | return "SpeakerInfo{" +
11 | "speaker='" + speaker + '\'' +
12 | ", emotion='" + emotion + '\'' +
13 | ", character='" + character + '\'' +
14 | '}';
15 | }
16 |
17 | public String getSpeaker() {
18 | return speaker;
19 | }
20 |
21 | public void setSpeaker(String speaker) {
22 | this.speaker = speaker;
23 | }
24 |
25 | public String getEmotion() {
26 | return emotion;
27 | }
28 |
29 | public void setEmotion(String emotion) {
30 | this.emotion = emotion;
31 | }
32 |
33 | public String getCharacter() {
34 | return character;
35 | }
36 |
37 | public void setCharacter(String character) {
38 | this.character = character;
39 | }
40 |
41 | public SpeakerInfo() {
42 | }
43 |
44 | public SpeakerInfo(String speaker, String emotion, String character) {
45 | this.speaker = speaker;
46 | this.emotion = emotion;
47 | this.character = character;
48 | }
49 | }
--------------------------------------------------------------------------------
/src/main/java/com/fjut/noveltts/utils/HandleAudioUtil.java:
--------------------------------------------------------------------------------
1 | package com.fjut.noveltts.utils;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import com.fjut.noveltts.entity.SentenceInfo;
5 | import org.springframework.beans.factory.annotation.Value;
6 | import org.springframework.stereotype.Component;
7 |
8 | import javax.sound.sampled.AudioFileFormat;
9 | import javax.sound.sampled.AudioInputStream;
10 | import javax.sound.sampled.AudioSystem;
11 | import java.io.*;
12 | import java.net.HttpURLConnection;
13 | import java.net.URL;
14 | import java.nio.charset.StandardCharsets;
15 | import java.nio.file.Files;
16 | import java.util.*;
17 |
18 | import static com.fjut.noveltts.utils.HandleTextUtil.formatArticle;
19 |
20 | @Component
21 | public class HandleAudioUtil {
22 | //将一段文本转换为音频
23 | private static boolean getAudio(String filePath,String character,String emotion,String text,Integer batch_size,Double speed) throws IOException {
24 |
25 | String url = ProgramUtil.getConfigByKey("url");
26 | if(url==null||url.isEmpty())
27 | System.err.println("HandleAudioUtil=>getAudio:未配置url");
28 | URL obj = new URL(url+"tts");
29 | //URL obj = new URL("http://localhost:5000/tts");
30 | HttpURLConnection con = (HttpURLConnection) obj.openConnection();
31 |
32 | // 设置请求方法
33 | con.setRequestMethod("POST");
34 | // 发送Post请求
35 | con.setDoOutput(true);
36 | // 创建一个Map来存储参数
37 | Map params = new HashMap<>();
38 | if(character!=null&&!character.isEmpty())
39 | params.put("character", character);//早期版本GPT-Sovits-Interface中参数名为cha_name
40 | if(emotion!=null&&!emotion.isEmpty())
41 | params.put("emotion", emotion);//早期版本GPT-Sovits-Interface中参数名为cha_name
42 | if(batch_size!=null)
43 | params.put("batch_size", batch_size);
44 | if(text!=null&&!text.isEmpty())
45 | params.put("text", text);
46 | if(speed!=null)
47 | params.put("speed", speed);
48 | params.put("text_language","中文");
49 | // 将Map转换为JSON格式的字符串
50 | ObjectMapper objectMapper = new ObjectMapper();
51 | String jsonParams = objectMapper.writeValueAsString(params);
52 | System.out.println(jsonParams);
53 | // 设置请求头,表示我们发送的是JSON格式的数据
54 | con.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
55 |
56 | OutputStream os = con.getOutputStream();
57 | os.write(jsonParams.getBytes(StandardCharsets.UTF_8));
58 | os.flush();
59 | os.close();
60 | // 发送请求并获取响应码
61 | int responseCode = con.getResponseCode();
62 |
63 | // 获取输入流
64 | InputStream inputStream = con.getInputStream();
65 |
66 | // 创建一个输出流,用于写入文件
67 | OutputStream outputStream = new FileOutputStream(filePath);
68 | // 创建一个缓冲区
69 | byte[] buffer = new byte[1024];
70 | int bytesRead;
71 | // 在循环中读取数据,直到没有更多数据可读
72 | while ((bytesRead = inputStream.read(buffer)) != -1) {
73 | outputStream.write(buffer, 0, bytesRead);
74 | }
75 | // 关闭输入流和输出流
76 | inputStream.close();
77 | outputStream.close();
78 | return responseCode== 200;
79 | }
80 |
81 | public static boolean textToAudio(ArrayList text, String path) {
82 | try {
83 | int index = 0;
84 | String title=text.get(0).getSentence().replace("。","");
85 | String dir = path + "audio/" +title+ "/";
86 | File file = new File(dir);
87 | if(!file.exists())
88 | file.mkdirs();
89 |
90 | for (SentenceInfo sentenceInfo : text) {
91 | String sentence = sentenceInfo.getSentence();
92 | String[] split = sentence.split("(?<=[。!?;……])");
93 | for (String s : split) {
94 | if(!s.matches(".*[\u4e00-\u9fa5]+.*"))
95 | continue;
96 | System.out.println(s);
97 | boolean audio = getAudio(dir+(index++) + ".wav", sentenceInfo.getCharacter(), sentenceInfo.getEmotion(), s, sentenceInfo.getBatchSize(), sentenceInfo.getSpeed());
98 | }
99 | // System.out.println(sentenceInfo.getSentence());
100 | // boolean audio = getAudio(dir+(index++) + ".wav", sentenceInfo.getCharacter(), sentenceInfo.getEmotion(), sentenceInfo.getSentence(), sentenceInfo.getBatchSize(), sentenceInfo.getSpeed());
101 | }
102 | mergeAndDeleteAudioFiles(path + "audio/", title);
103 | file.delete();
104 | } catch (Exception e) {
105 | System.err.println("textToAudio=>"+e);
106 | }
107 | return true;
108 | }
109 |
110 | private static void mergeAndDeleteAudioFiles(String directoryPath, String title) throws Exception {
111 | // 创建一个File对象,表示指定路径的目录
112 | File dir = new File(directoryPath+title);
113 | // 获取目录下所有以.wav结尾的文件
114 | File[] files = dir.listFiles((d, name) -> name.endsWith(".wav"));
115 | // 如果没有找到任何.wav文件,抛出异常
116 | if (files == null || files.length == 0) {
117 | throw new Exception("No wav files found in the directory.");
118 | }
119 | Arrays.sort(files, Comparator.comparingInt(file -> Integer.parseInt(file.getName().replace(".wav", ""))));
120 |
121 | // 获取第一个.wav文件的音频输入流和音频文件格式
122 | AudioInputStream audioInputStream;
123 | AudioFileFormat format = AudioSystem.getAudioFileFormat(files[0]);
124 |
125 | // 创建一个字节数组输出流,用于存储所有.wav文件的数据
126 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
127 | // 创建一个缓冲区
128 | byte[] buffer = new byte[1024];
129 | int read;
130 |
131 | // 遍历所有.wav文件
132 | for (File file : files) {
133 | // 获取当前.wav文件的音频输入流
134 | audioInputStream = AudioSystem.getAudioInputStream(file);
135 | // 读取音频输入流的数据,并写入到字节数组输出流中
136 | while ((read = audioInputStream.read(buffer)) > 0) {
137 | outputStream.write(buffer, 0, read);
138 | }
139 | // 关闭音频输入流
140 | audioInputStream.close();
141 | }
142 |
143 | // 将字节数组输出流转换为字节数组
144 | byte[] outputBytes = outputStream.toByteArray();
145 | // 创建一个字节数组输入流
146 | InputStream byteArrayInputStream = new ByteArrayInputStream(outputBytes);
147 | // 将字节数组输入流转换为音频输入流
148 | AudioInputStream outputAudioInputStream = new AudioInputStream(byteArrayInputStream, format.getFormat(), outputBytes.length / format.getFormat().getFrameSize());
149 | // 将音频输入流写入到一个新的.wav文件中
150 | AudioSystem.write(outputAudioInputStream, format.getType(), new File(directoryPath + title + ".wav"));
151 |
152 | // 删除原先的.wav文件
153 | for (File file : files) {
154 | Files.delete(file.toPath());
155 | }
156 | }
157 |
158 | // public static void main(String[] args) throws IOException {
159 | // BufferedReader bufferedReader = new BufferedReader(new FileReader("E:\\audio\\text.txt"));
160 | // String str;
161 | // int index=0;
162 | // while((str=bufferedReader.readLine())!=null){
163 | // System.out.println(str);
164 | // getAudio("E:\\audio\\res2\\"+(index++)+(str.length()>100?str.substring(0,100):str)+".wav","卡芙卡","悲伤",str,50,1.0);
165 | // }
166 | // }
167 | }
--------------------------------------------------------------------------------
/src/main/java/com/fjut/noveltts/utils/HandleModelConfigUtil.java:
--------------------------------------------------------------------------------
1 | package com.fjut.noveltts.utils;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 |
5 | import java.io.File;
6 | import java.io.FileWriter;
7 | import java.io.FilenameFilter;
8 | import java.io.IOException;
9 | import java.util.HashMap;
10 |
11 | //生成模型配置文件
12 | public class HandleModelConfigUtil {
13 | //要转化的模型来自https://space.bilibili.com/518098961/?spm_id_from=333.999.0.0,格式如下
14 | // 参考音频(文件夹)
15 | // xxx.pth(文件)
16 | // xxx.ckpt(文件)
17 | // 训练日志.log(文件)
18 |
19 | //以下方法用于用于创建模型配置文件,GPT-Sovits-Interface项目中的模型配置文件格式如下
20 | // {
21 | // "emotion_list": {
22 | // "default": {
23 | // "prompt_language": "中文",
24 | // "prompt_text": "激动说话-既然密钥找到了就赶快开始吧,电梯的权限用那边的操作台就能解锁。",
25 | // "ref_wav_path": "参考音频\\激动说话-既然密钥找到了就赶快开始吧,电梯的权限用那边的操作台就能解锁。.wav"
26 | // }
27 | // },
28 | // "简介": "这是一个配置文件适用于https://github.com/X-T-E-R/TTS-for-GPT-soVITS,是一个简单好用的前后端项目",
29 | // "sovits_path": "阿兰_e25_s525.pth",
30 | // "version": "1.0.1",
31 | // "gpt_path": "阿兰-e10.ckpt"
32 | // }
33 |
34 | //创建配置文件
35 | public static void setConfigFile(String filePath){
36 | try {
37 | //打开模型存放文件夹,如"E:\GPT-Sovits\GPT-SoVITS-Inference\trained"
38 | File modelFolders = new File(filePath);
39 | File[] modelsFolders = modelFolders.listFiles(File::isDirectory);
40 | for (File modelFolder : modelsFolders) {
41 | //遍历文件夹,路径如"E:\GPT-Sovits\GPT-SoVITS-Inference\trained\阿兰
42 | File modelPath = modelFolder.getCanonicalFile();
43 |
44 | //设置基础配置
45 | HashMap configMap = new HashMap<>();
46 | setBaseConfig(modelFolder,configMap);
47 | //设置情感配置
48 | HashMap emoConfig = setEmoConfig(modelFolder);
49 | if(emoConfig==null)
50 | continue;
51 | configMap.put("emotion_list",emoConfig);
52 | // 将对象转化为 JSON 格式的字符串
53 | String json = new ObjectMapper().writeValueAsString(configMap);
54 |
55 | //将文件写入到指定文件夹下的infer_config.json
56 | File out = new File(modelPath + "/" + "infer_config.json");
57 | //如果文件不存在则创建文件
58 | if (!out.exists())
59 | out.createNewFile();
60 |
61 | //写入文件,刷新缓冲区,关闭文件
62 | FileWriter fileWriter = new FileWriter(out);
63 | fileWriter.write(json);
64 | fileWriter.flush();
65 | fileWriter.close();
66 | }
67 | } catch (IOException e) {
68 | System.err.println("setConfigFile=>"+e);
69 | }
70 | }
71 |
72 | //创建基础配置文件
73 | private static void setBaseConfig(File file, HashMap configMap){
74 | //file路径如"E:\GPT-Sovits\GPT-SoVITS-Inference\trained\阿兰
75 | configMap.put("version","1.0.1");
76 | configMap.put("简介","这是一个配置文件适用于https://github.com/X-T-E-R/TTS-for-GPT-soVITS,是一个简单好用的前后端项目");
77 | String[] list = file.list((dir, name) -> name.endsWith(".ckpt") || name.endsWith(".pth"));
78 | //遍历文件夹下的文件,将.pth文件赋值给sovits_path,将.ckpt文件赋值给gpt_path
79 | for (String path : list) {
80 | if(path.endsWith(".ckpt"))
81 | configMap.put("gpt_path",path);
82 | else
83 | configMap.put("sovits_path",path);
84 | }
85 | }
86 |
87 | //用于创建情感配置文件
88 | private static HashMap setEmoConfig(File file){
89 | //file路径如E:\GPT-Sovits\GPT-SoVITS-Inference\trained\阿兰
90 | HashMap res = new HashMap<>();
91 | File[] files = file.listFiles(File::isDirectory);
92 | if(files==null||files.length<=0)
93 | return null;
94 | File[] audioDirList = files[0].listFiles();
95 | //开始遍历参考音频,audioDir路径为E:\GPT-Sovits\GPT-SoVITS-Inference\trained\阿兰\参考音频
96 | for (File audioDir : audioDirList) {
97 | HashMap map = new HashMap<>();
98 | map.put("prompt_language","中文");
99 | String dirName=audioDir.getName();
100 | //如果是文件,则直接赋值给default,路径为E:\GPT-Sovits\GPT-SoVITS-Inference\trained\阿兰\参考音频\xxx.wav
101 | if(audioDir.isFile()){
102 | String[] split = dirName.split("-");
103 | map.put("ref_wav_path","参考音频/"+dirName);
104 | if(dirName.contains("-")){
105 | map.put("prompt_text",split[1].replace(".wav",""));
106 | res.put(split[0],map);
107 | }else{
108 | map.put("prompt_text",dirName.replace(".wav",""));
109 | res.put("default",map);
110 | }
111 | }else{
112 | //如果是文件夹,则遍历文件夹下的文件,找出最大的文件
113 | File[] audioFileList = audioDir.listFiles();
114 | File maxFile = null;
115 | long maxSize = 0;
116 | for (File audioFile : audioFileList) {
117 | if (audioFile.length() > maxSize&&!audioFile.getName().replace(".wav","").matches(".*[a-zA-Z].*")) {
118 | maxSize = audioFile.length();
119 | maxFile = audioFile;
120 | }
121 | }
122 | if(maxFile!=null){
123 | //ref_wav_path路径形如E:\GPT-Sovits\GPT-SoVITS-Inference\trained\阿兰\参考音频\开心(或其他情绪)\xxx.wav
124 | map.put("ref_wav_path","参考音频/"+dirName+"/"+maxFile.getName());
125 | map.put("prompt_text",maxFile.getName().replace(".wav","").replaceAll("【.*?】", ""));
126 | res.put(dirName,map);
127 | }
128 | }
129 | }
130 | return res;
131 | }
132 | }
--------------------------------------------------------------------------------
/src/main/java/com/fjut/noveltts/utils/HandleTextUtil.java:
--------------------------------------------------------------------------------
1 | package com.fjut.noveltts.utils;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import com.fjut.noveltts.entity.SentenceInfo;
5 |
6 | import java.io.*;
7 | import java.util.*;
8 | import java.util.regex.Matcher;
9 | import java.util.regex.Pattern;
10 |
11 | public class HandleTextUtil {
12 |
13 |
14 |
15 | //将一本小说按照“第xxx章 标题”,分割成多个文件,每个文件对应一章
16 | public static void splitArticle(String path){
17 | try {
18 | int i=1;
19 | //创建文件夹
20 | File resFile = new File(path.replace(".txt", ""));
21 | if (!resFile.exists()) {
22 | resFile.mkdirs();
23 | }
24 |
25 | //创建文件读取流
26 | BufferedReader bufferedReader = new BufferedReader(new FileReader(path));
27 | //用于储存读取到的行内容
28 | String line = null;
29 | //在读取到章节名称之后,储存章节名,用于在while中(nextLine != null)利用||截断特性,跳过此处line的读取
30 | String nextLine = null;
31 |
32 | //定义一个正则表达式,匹配“第xxx章 标题”
33 | Pattern chapterPattern = Pattern.compile("^.*第.{0,15}章.*$");
34 |
35 | //读取文件内容
36 | while ((nextLine != null) || (line = bufferedReader.readLine()) != null) {
37 | //如果匹配到章节名称
38 | if (chapterPattern.matcher(line).matches()) {
39 | //以章节名创建文件,i用于文件排序
40 | String chapterName = (i++)+line;
41 | File chapterFile = new File(resFile, chapterName + ".txt");
42 | //创建文件写入流
43 | BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(chapterFile));
44 | // 将章节名称写入到文件中,并换行,使用“。”包裹,避免对话切割时与正文相连
45 | bufferedWriter.write('“' + chapterName.replace("“","").replace("”","") + "。”");
46 | bufferedWriter.newLine();
47 |
48 | //从标题后开始读取,直到下一个章节名称之前
49 | while ((nextLine = bufferedReader.readLine()) != null) {
50 | //如果匹配到章节名称,赋值给nextLine和line,跳出循环
51 | if (chapterPattern.matcher(nextLine).matches()) {
52 | line = nextLine;
53 | break;
54 | }
55 | bufferedWriter.write(nextLine.replace("?","?").replace("!","!")
56 | .replace(".","。").replace(";",";").replace("「","“").replace("」","”").replace("……?","啊"));
57 | bufferedWriter.newLine();
58 | }
59 | bufferedWriter.close();
60 | }
61 | }
62 | bufferedReader.close();
63 | } catch (IOException e) {
64 | System.err.println("splitArticle=>"+e);
65 | }
66 | }
67 |
68 |
69 | //读取一章的内容,并将其分隔为对话和非对话两部分
70 | //对话部分以“ 。”或 “ !”或 “ ……”或 “ ?”包裹,非对话部分以对话部分之间的内容。如果文章未按照规范格式书写,可能会出现错误
71 | public static ArrayList formatArticle(String path) {
72 | try {
73 | //创建集合储存对话和非对话
74 | ArrayList SentenceInfos = new ArrayList<>();
75 | //根据文件名创建文件读取流
76 | FileReader fileReader = new FileReader(path);
77 | char[] str = new char[100000];
78 | //用于替换换行符
79 | String wrap = "\r\n";
80 | int legth = 0;
81 | //读取文件内容
82 | while ((legth=fileReader.read(str)) != -1) {
83 | StringBuilder string = new StringBuilder(new String(str));
84 | // 定义一个正则表达式,匹配任何以句号、感叹号或问号结束的字符串
85 | Pattern pattern = Pattern.compile("“[^“”]*?(“[^“”]*?”[^“”]*?)*?[,;。!?……—]”");
86 | // 获取一个 Matcher,用于匹配当前行
87 | Matcher matcher = pattern.matcher(string);
88 | int begin = 0;
89 | // 查找匹配的字符串
90 | while (matcher.find()) {
91 | //如果匹配的字符串不是从头开始的,将匹配字符串之前的字符串加入到集合中,对话之前的就是非对话
92 | if (matcher.start() != begin) {
93 | //截取非对话部分,替换无效字符
94 | String replace = string.substring(begin, matcher.start()).replace(wrap, "").replace(" ", "").replace(" ","");//replace((char)12288,' ').replace(" ","");
95 | //如果不为空,将非对话部分加入到集合中
96 | if (!replace.isEmpty())
97 | SentenceInfos.add(new SentenceInfo(false, replace));
98 | }
99 | //截取对话部分,替换无效字符
100 | String replace = matcher.group().replace(wrap, "").replace(" ", "").replace(" ","");//replace((char)12288,' ').replace(" ","");
101 | SentenceInfos.add(new SentenceInfo(true, replace));
102 | begin = matcher.end();
103 | }
104 | if(begin!=legth)
105 | SentenceInfos.add(new SentenceInfo(false, string.substring(begin,legth)));
106 | }
107 | //关闭文件读取流
108 | fileReader.close();
109 |
110 | setSpeaker(SentenceInfos);
111 | //返回结果集
112 | return SentenceInfos;
113 | } catch (IOException e) {
114 | System.err.println("formatArticle=>"+e);
115 | return null;
116 | }
117 | }
118 |
119 | public static void saveFormatArticle(String path,ArrayList sentenceInfo) {
120 | try {
121 | // 创建 ObjectMapper 对象
122 | ObjectMapper mapper = new ObjectMapper();
123 | // 将 ArrayList 对象转换为 JSON 字符串
124 | String json = mapper.writeValueAsString(sentenceInfo);
125 | // 创建 FileWriter 对象
126 | FileWriter writer = new FileWriter(path);
127 | // 将 JSON 字符串写入到文件中
128 | writer.write(json);
129 | // 关闭 FileWriter
130 | writer.close();
131 | } catch (IOException e) {
132 | System.err.println("saveFormatArticle=>"+e);
133 | }
134 | }
135 |
136 | //从前后旁白中找出说话者
137 | private static void setSpeaker(ArrayList list) {
138 | int index = 0, size = list.size();
139 | SentenceInfo tmp = null;
140 | //读取默认配置
141 | double speed= ProgramUtil.getConfigByKey("speed") == null ? 1.0 : Double.parseDouble(ProgramUtil.getConfigByKey("speed"));
142 | int batchSize= ProgramUtil.getConfigByKey("batchSize") == null ? 1 : Integer.parseInt(ProgramUtil.getConfigByKey("batchSize"));
143 |
144 | String defaultNarrationCharacter = ProgramUtil.getConfigByKey("defaultNarrationCharacter");
145 | String defaultNarrationEmotion = ProgramUtil.getConfigByKey("defaultNarrationEmotion");
146 | //遍历集合中的所有句子
147 | for (SentenceInfo sentenceInfo : list) {
148 | sentenceInfo.setSpeed(speed);
149 | sentenceInfo.setBatchSize(batchSize);
150 | sentenceInfo.setSpeaker("默认旁白角色");
151 | sentenceInfo.setCharacter(defaultNarrationCharacter);
152 | sentenceInfo.setEmotion(defaultNarrationEmotion);
153 | //如果当前句子是对话,从前后旁白中找出说话者(近似查找
154 | if (sentenceInfo.isDialogue()) {
155 | //如果前一个存在,且不是对话,从前一个句子中找出说话者,否则从后一个句子中找出说话者,如果都不存在,不做处理
156 | if (index - 1 >= 0 && (!((tmp = list.get(index - 1)).isDialogue()))) {
157 | getMinSpeaker(tmp.getSentence(), sentenceInfo);
158 | } else if (index + 1 < size && (!((tmp = list.get(index + 1)).isDialogue()))) {
159 | getMinSpeaker(tmp.getSentence(), sentenceInfo);
160 | }
161 | }
162 | index++;
163 | }
164 | }
165 |
166 | //找到传入句子(对话的前一句或者后一句)中存在与set集合中,第一个出现的说话者
167 | private static void getMinSpeaker(String lastOrNextSentence, SentenceInfo nowSentence) {
168 | //获取所有配置文件
169 | Map characterMap = ProgramUtil.getCharacterConfig();
170 | Set speakerSet =characterMap.keySet();
171 |
172 | //如果是章节名,不做处理
173 | Pattern chapterPattern = Pattern.compile("^.*第.{0,15}章.*$");
174 | String sentence = nowSentence.getSentence();
175 | if (chapterPattern.matcher(sentence).matches()) {
176 | String res = sentence.replace("“", "").replace("”", "")
177 | .replace("。", "").replace("章", "章。")
178 | .replaceAll("\\d", "");
179 | nowSentence.setSentence(res);
180 | nowSentence.setDialogue(false);
181 | return;
182 | }
183 |
184 | int minIndex = Integer.MAX_VALUE;
185 | String minSpeaker = null;
186 | //找到传入句子(对话的前一句或者后一句)中存在与set集合中,第一个出现的说话者
187 | for (String s : speakerSet) {
188 | int i = lastOrNextSentence.indexOf(s);
189 | if (i != -1 && i < minIndex) {
190 | minIndex = i;
191 | minSpeaker = s;
192 | }
193 | }
194 | //如果找到了说话者,将说话者赋值给当前句子,否则不做处理
195 | if (minSpeaker != null) {
196 | String[] split= characterMap.get(minSpeaker).split("@&");
197 | nowSentence.setCharacter(split[0]);
198 | nowSentence.setEmotion(split[1]);
199 | nowSentence.setSpeaker(minSpeaker);
200 | }else{
201 | nowSentence.setSpeaker("默认对话角色");
202 | nowSentence.setCharacter(ProgramUtil.getConfigByKey("defaultDialogueCharacter"));
203 | nowSentence.setEmotion(ProgramUtil.getConfigByKey("defaultDialogueEmotion"));
204 | }
205 | }
206 |
207 |
208 | }
--------------------------------------------------------------------------------
/src/main/java/com/fjut/noveltts/utils/ProgramUtil.java:
--------------------------------------------------------------------------------
1 | package com.fjut.noveltts.utils;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 |
5 | import java.io.*;
6 | import java.net.URI;
7 | import java.net.http.HttpClient;
8 | import java.net.http.HttpRequest;
9 | import java.net.http.HttpResponse;
10 | import java.util.*;
11 |
12 | //用于读写txt文件中写入配置
13 | public class ProgramUtil {
14 | private static String path="./config.txt";
15 | //向配置文件中添加配置
16 | public static void addConfig(Map map) {
17 | try {
18 | Properties properties = new Properties();
19 | properties.load(new FileReader(path));
20 | properties.putAll(map);
21 | properties.store(new FileWriter(path), null);
22 | } catch (IOException e) {
23 | System.err.println("addConfig=>"+e);
24 | }
25 | }
26 | //向配置文件中添加配置
27 | public static void addConfig(String key,String value) {
28 | try {
29 | Properties properties = new Properties();
30 | properties.load(new FileReader(path));
31 | properties.setProperty(key,value);
32 | properties.store(new FileWriter(path), null);
33 | } catch (IOException e) {
34 | System.err.println("addConfig=>"+e);
35 | }
36 | }
37 |
38 | //返回配置文件中的所有配置
39 | public static Map getconfig() {
40 | try {
41 | Properties properties = new Properties();
42 | properties.load(new FileReader(path));
43 | Map stringMap = new HashMap<>();
44 | for (Map.Entry