├── .gitattributes ├── .gitignore ├── Aitalk ├── Aitalk.csproj └── source │ ├── AitalkCore.cs │ ├── AitalkParameter.cs │ └── AitalkWrapper.cs ├── Injecter ├── App.config ├── Injecter.cs ├── Injecter.csproj ├── Program.cs ├── Properties │ ├── AssemblyInfo.cs │ └── app.manifest └── packages.config ├── LICENSE ├── README.md ├── VoiceroidDaemon ├── Controllers │ ├── ConvertTextApiController.cs │ ├── GetKeyApiController.cs │ ├── HomeController.cs │ └── SpeechTextApiController.cs ├── Models │ ├── ErrorViewModel.cs │ ├── SpeakerModel.cs │ ├── SpeakerSettingModel.cs │ ├── SpeechModel.cs │ └── SystemSettingModel.cs ├── Program.cs ├── Setting.cs ├── SettingJson.cs ├── SettingValues.cs ├── Startup.cs ├── Views │ ├── Home │ │ ├── Contact.cshtml │ │ ├── Index.cshtml │ │ ├── Privacy.cshtml │ │ ├── SpeakerSetting.cshtml │ │ └── SystemSetting.cshtml │ ├── Shared │ │ ├── Error.cshtml │ │ ├── _CookieConsentPartial.cshtml │ │ ├── _Layout.cshtml │ │ └── _ValidationScriptsPartial.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── VoiceroidDaemon.csproj ├── appsettings.Development.json ├── appsettings.json └── wwwroot │ └── css │ ├── bootstrap-theme.min.css │ ├── bootstrap.min.css │ └── site.min.css ├── voiceroid_daemon.sln └── voiceroidd ├── App.config ├── Config.cs ├── Injecter.cs ├── Program.cs ├── Properties └── AssemblyInfo.cs ├── app.manifest ├── packages.config └── voiceroidd.csproj /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | 332 | setting.json 333 | -------------------------------------------------------------------------------- /Aitalk/Aitalk.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | AnyCPU 6 | 7 | 8 | 9 | AnyCPU 10 | 11 | 12 | 13 | AnyCPU 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Aitalk/source/AitalkCore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Runtime.InteropServices; 4 | using System.Runtime.Serialization; 5 | 6 | namespace Aitalk 7 | { 8 | internal static class AitalkCore 9 | { 10 | [DllImport("aitalked.dll", EntryPoint = "_AITalkAPI_Init@4")] 11 | public static extern Result Init([In] ref Config config); 12 | 13 | [DllImport("aitalked.dll", EntryPoint = "_AITalkAPI_End@0")] 14 | public static extern Result End(); 15 | 16 | [DllImport("aitalked.dll", EntryPoint = "_AITalkAPI_LangLoad@4")] 17 | public static extern Result LangLoad(string language_name); 18 | 19 | [DllImport("aitalked.dll", EntryPoint = "_AITalkAPI_LangClear@0")] 20 | public static extern Result LangClear(); 21 | 22 | [DllImport("aitalked.dll", EntryPoint = "_AITalkAPI_VoiceLoad@4")] 23 | public static extern Result VoiceLoad(string voice_name); 24 | 25 | [DllImport("aitalked.dll", EntryPoint = "_AITalkAPI_VoiceClear@0")] 26 | public static extern Result VoiceClear(); 27 | 28 | [DllImport("aitalked.dll", EntryPoint = "_AITalkAPI_ReloadPhraseDic@4")] 29 | public static extern Result ReloadPhraseDic(string dictionary_path); 30 | 31 | [DllImport("aitalked.dll", EntryPoint = "_AITalkAPI_ReloadSymbolDic@4")] 32 | public static extern Result ReloadSymbolDic(string dictionary_path); 33 | 34 | [DllImport("aitalked.dll", EntryPoint = "_AITalkAPI_ReloadWordDic@4")] 35 | public static extern Result ReloadWordDic(string dictionary_path); 36 | 37 | [DllImport("aitalked.dll", EntryPoint = "_AITalkAPI_GetParam@8")] 38 | public static extern Result GetParam(IntPtr param, ref int written_bytes); 39 | 40 | [DllImport("aitalked.dll", EntryPoint = "_AITalkAPI_SetParam@4")] 41 | public static extern Result SetParam(IntPtr param); 42 | 43 | [DllImport("aitalked.dll", EntryPoint = "_AITalkAPI_TextToKana@12")] 44 | public static extern Result TextToKana(out int job_id, [In] ref JobParam job_param, [In] byte[] text); 45 | 46 | [DllImport("aitalked.dll", EntryPoint = "_AITalkAPI_GetKana@20")] 47 | public static extern Result GetKana(int job_id, [Out, MarshalAs(UnmanagedType.LPArray)] byte[] buffer, int buffer_capacity, out int read_bytes, out int position); 48 | 49 | [DllImport("aitalked.dll", EntryPoint = "_AITalkAPI_CloseKana@8")] 50 | public static extern Result CloseKana(int job_id, int zero = 0); 51 | 52 | [DllImport("aitalked.dll", EntryPoint = "_AITalkAPI_TextToSpeech@12")] 53 | public static extern Result TextToSpeech(out int job_id, [In] ref JobParam job_param, string text); 54 | 55 | [DllImport("aitalked.dll", EntryPoint = "_AITalkAPI_GetData@16")] 56 | public static extern Result GetData(int job_id, [Out, MarshalAs(UnmanagedType.LPArray)] byte[] buffer, int buffer_capacity, out int read_samples); 57 | 58 | [DllImport("aitalked.dll", EntryPoint = "_AITalkAPI_CloseSpeech@8")] 59 | public static extern Result CloseSpeech(int job_id, int zero = 0); 60 | 61 | public enum EventReason 62 | { 63 | TextBufferFull = 101, 64 | TextBufferFlush = 102, 65 | TextBufferClose = 103, 66 | RawBufferFull = 201, 67 | RawBufferFlush = 202, 68 | RawBufferClose = 203, 69 | PhoneticLabel = 301, 70 | Bookmark = 302, 71 | AutoBookmark = 303 72 | } 73 | 74 | public enum Result 75 | { 76 | Success = 0, 77 | InternalError = -1, 78 | Unsupported = -2, 79 | InvalidArgument = -3, 80 | WaitTimeout = -4, 81 | NotInitialized = -10, 82 | AlreadyInitialized = 10, 83 | NotLoaded = -11, 84 | AlreadyLoaded = 11, 85 | Insufficient = -20, 86 | PartiallyRegistered = 21, 87 | LicenseAbsent = -100, 88 | LicenseExpired = -101, 89 | LicenseRejected = -102, 90 | TooManyJobs = -201, 91 | InvalidJobId = -202, 92 | JobBusy = -203, 93 | NoMoreData = 204, 94 | OutOfMemory = -206, 95 | FileNotFound = -1001, 96 | PathNotFound = -1002, 97 | ReadFault = -1003, 98 | CountLimit = -1004, 99 | UserDictionaryLocked = -1011, 100 | UserDictionaryNoEntry = -1012 101 | } 102 | 103 | public enum Status 104 | { 105 | WrongState = -1, 106 | InProgress = 10, 107 | StillRunning = 11, 108 | Done = 12 109 | } 110 | 111 | public enum JobInOut 112 | { 113 | PlainToWave = 11, 114 | KanaToWave = 12, 115 | JeitaToWave = 13, 116 | PlainToKana = 21, 117 | KanaToJeita = 32 118 | } 119 | 120 | [Flags] 121 | public enum ExtendFormat 122 | { 123 | None = 0x0, 124 | JeitaRuby = 0x1, 125 | AutoBookmark = 0x10 126 | } 127 | 128 | [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)] 129 | public struct Config 130 | { 131 | public int VoiceDbSampleRate; 132 | 133 | [MarshalAs(UnmanagedType.LPStr)] 134 | public string VoiceDbDirectory; 135 | 136 | public int TimeoutMilliseconds; 137 | 138 | [MarshalAs(UnmanagedType.LPStr)] 139 | public string LicensePath; 140 | 141 | [MarshalAs(UnmanagedType.LPStr)] 142 | public string AuthenticateCodeSeed; 143 | 144 | public int ReservedZero; 145 | } 146 | 147 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 148 | public struct JobParam 149 | { 150 | public JobInOut ModeInOut; 151 | public IntPtr UserData; 152 | } 153 | 154 | [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)] 155 | public struct TtsParam 156 | { 157 | public const int VoiceNameLength = 80; 158 | 159 | public int Size; 160 | 161 | public TextBufferCallbackType TextBufferCallback; 162 | 163 | public RawBufferCallbackType RawBufferCallback; 164 | 165 | public TtsEventCallbackType TtsEventCallback; 166 | 167 | public int TextBufferCapacityInBytes; 168 | 169 | public int RawBufferCapacityInBytes; 170 | 171 | public float Volume; 172 | 173 | public int PauseBegin; 174 | 175 | public int PauseTerm; 176 | 177 | public ExtendFormat ExtendFormatFlags; 178 | 179 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = VoiceNameLength)] 180 | public string VoiceName; 181 | 182 | public JeitaParam Jeita; 183 | 184 | public int NumberOfSpeakers; 185 | 186 | public int ReservedZero; 187 | 188 | [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)] 189 | public struct JeitaParam 190 | { 191 | public const int ControlLength = 12; 192 | 193 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = VoiceNameLength)] 194 | public string FemaleName; 195 | 196 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = VoiceNameLength)] 197 | public string MaleName; 198 | 199 | public int PauseMiddle; 200 | 201 | public int PauseLong; 202 | 203 | public int PauseSentence; 204 | 205 | /// 206 | /// JEITA TT-6004を参照せよ 207 | /// 208 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = ControlLength)] 209 | public string Control; 210 | } 211 | 212 | [StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)] 213 | [DataContract] 214 | public class SpeakerParam 215 | { 216 | [DataMember] 217 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = VoiceNameLength)] 218 | public string VoiceName; 219 | 220 | [DataMember] 221 | public float Volume; 222 | 223 | [DataMember] 224 | public float Speed; 225 | 226 | [DataMember] 227 | public float Pitch; 228 | 229 | [DataMember] 230 | public float Range; 231 | 232 | [DataMember] 233 | public int PauseMiddle; 234 | 235 | [DataMember] 236 | public int PauseLong; 237 | 238 | [DataMember] 239 | public int PauseSentence; 240 | 241 | [DataMember] 242 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = VoiceNameLength)] 243 | public string StyleRate; 244 | } 245 | 246 | public delegate int TextBufferCallbackType(EventReason reason, int job_id, IntPtr user_data); 247 | 248 | public delegate int RawBufferCallbackType(EventReason reason, int job_id, long tick, IntPtr user_data); 249 | 250 | public delegate int TtsEventCallbackType(EventReason reason, int job_id, long tick, string name, IntPtr user_data); 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /Aitalk/source/AitalkParameter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.IO; 6 | using System.Runtime.Serialization; 7 | using System.Runtime.Serialization.Json; 8 | 9 | namespace Aitalk 10 | { 11 | public class AitalkParameter 12 | { 13 | /// 14 | /// コンストラクタ。 15 | /// 16 | /// ボイスライブラリ名 17 | /// パラメータ 18 | /// 話者のパラメータリスト 19 | internal AitalkParameter(string voice_db_name, AitalkCore.TtsParam tts_param, AitalkCore.TtsParam.SpeakerParam[] speaker_params) 20 | { 21 | VoiceDbName = voice_db_name; 22 | TtsParam = tts_param; 23 | SpeakerParameters = speaker_params; 24 | CurrentSpeakerName = SpeakerParameters[0].VoiceName; 25 | CurrentSpeakerParameter = SpeakerParameters[0]; 26 | } 27 | 28 | /// 29 | /// JSON形式のバイト列に変換する 30 | /// 31 | /// JSON形式のバイト列 32 | public byte[] ToJson() 33 | { 34 | // 一時的な構造体にパラメータを格納する 35 | ParameterJson parameter; 36 | parameter.VoiceDbName = VoiceDbName; 37 | parameter.Speakers = SpeakerParameters; 38 | 39 | // JSONにシリアライズする 40 | using (var stream = new MemoryStream()) 41 | { 42 | using (var writer = JsonReaderWriterFactory.CreateJsonWriter(stream, Encoding.UTF8, true, true, " ")) 43 | { 44 | var serializer = new DataContractJsonSerializer(typeof(ParameterJson)); 45 | serializer.WriteObject(writer, parameter); 46 | writer.Flush(); 47 | } 48 | return stream.ToArray(); 49 | } 50 | } 51 | 52 | /// 53 | /// パラメータが変更されたときにtrueになる 54 | /// 55 | public bool IsParameterChanged { get; internal set; } = true; 56 | 57 | /// 58 | /// 仮名変換時のバッファサイズ(バイト数) 59 | /// 60 | public int TextBufferCapacityInBytes 61 | { 62 | get { return TtsParam.TextBufferCapacityInBytes; } 63 | } 64 | 65 | /// 66 | /// 音声変換時のバッファサイズ(バイト数) 67 | /// 68 | public int RawBufferCapacityInBytes 69 | { 70 | get { return TtsParam.RawBufferCapacityInBytes; } 71 | } 72 | 73 | /// 74 | /// trueのとき仮名変換結果に文節終了位置を埋め込む 75 | /// 76 | public bool AutoBookmark 77 | { 78 | get { return (TtsParam.ExtendFormatFlags & AitalkCore.ExtendFormat.AutoBookmark) != 0; } 79 | set 80 | { 81 | IsParameterChanged |= (value != AutoBookmark); 82 | if (value == true) 83 | { 84 | TtsParam.ExtendFormatFlags |= AitalkCore.ExtendFormat.AutoBookmark; 85 | } 86 | else 87 | { 88 | TtsParam.ExtendFormatFlags &= ~AitalkCore.ExtendFormat.AutoBookmark; 89 | } 90 | 91 | } 92 | } 93 | 94 | /// 95 | /// trueのとき仮名変換結果にJEITA規格のルビを使う 96 | /// 97 | public bool JeitaRuby 98 | { 99 | get { return (TtsParam.ExtendFormatFlags & AitalkCore.ExtendFormat.JeitaRuby) != 0; } 100 | set 101 | { 102 | IsParameterChanged |= (value != JeitaRuby); 103 | if (value == true) 104 | { 105 | TtsParam.ExtendFormatFlags |= AitalkCore.ExtendFormat.JeitaRuby; 106 | } 107 | else 108 | { 109 | TtsParam.ExtendFormatFlags &= ~AitalkCore.ExtendFormat.JeitaRuby; 110 | } 111 | } 112 | } 113 | 114 | /// 115 | /// マスター音量(0~5) 116 | /// 117 | public double MasterVolume 118 | { 119 | get { return TtsParam.Volume; } 120 | set 121 | { 122 | float value_f = (float)Math.Max(MinMasterVolume, Math.Min(value, MaxMasterVolume)); 123 | IsParameterChanged |= (value_f != TtsParam.Volume); 124 | TtsParam.Volume = value_f; 125 | } 126 | } 127 | public const double MaxMasterVolume = 5.0; 128 | public const double MinMasterVolume = 0.0; 129 | 130 | /// 131 | /// 話者の名前のリスト 132 | /// 133 | public string[] VoiceNames 134 | { 135 | get { return SpeakerParameters.Select(x => x.VoiceName).ToArray(); } 136 | } 137 | 138 | /// 139 | /// 選択中の話者 140 | /// 141 | public string CurrentSpeakerName 142 | { 143 | get { return TtsParam.VoiceName; } 144 | set 145 | { 146 | if (TtsParam.VoiceName == value) 147 | { 148 | return; 149 | } 150 | var speaker_parameter = SpeakerParameters.FirstOrDefault(x => x.VoiceName == value); 151 | if (speaker_parameter == null) 152 | { 153 | throw new AitalkException($"話者'{value}'は存在しません。"); 154 | } 155 | CurrentSpeakerParameter = speaker_parameter; 156 | TtsParam.VoiceName = value; 157 | IsParameterChanged = true; 158 | } 159 | } 160 | 161 | /// 162 | /// 音量(0~2) 163 | /// 164 | public double VoiceVolume 165 | { 166 | get { return CurrentSpeakerParameter.Volume; } 167 | set 168 | { 169 | float value_f = (float)Math.Max(MinVoiceVolume, Math.Min(value, MaxVoiceVolume)); 170 | IsParameterChanged |= (value_f != CurrentSpeakerParameter.Volume); 171 | CurrentSpeakerParameter.Volume = value_f; 172 | } 173 | } 174 | public const double MinVoiceVolume = 0.0; 175 | public const double MaxVoiceVolume = 2.0; 176 | 177 | /// 178 | /// 話速(0.5~4) 179 | /// 180 | public double VoiceSpeed 181 | { 182 | get { return CurrentSpeakerParameter.Speed; } 183 | set 184 | { 185 | float value_f = (float)Math.Max(MinVoiceSpeed, Math.Min(value, MaxVoiceSpeed)); 186 | IsParameterChanged |= (value_f != CurrentSpeakerParameter.Speed); 187 | CurrentSpeakerParameter.Speed = value_f; 188 | } 189 | } 190 | public const double MinVoiceSpeed = 0.5; 191 | public const double MaxVoiceSpeed = 4.0; 192 | 193 | /// 194 | /// 高さ(0.5~2) 195 | /// 196 | public double VoicePitch 197 | { 198 | get { return CurrentSpeakerParameter.Pitch; } 199 | set 200 | { 201 | float value_f = (float)Math.Max(MinVoicePitch, Math.Min(value, MaxVoicePitch)); 202 | IsParameterChanged |= (value_f != CurrentSpeakerParameter.Pitch); 203 | CurrentSpeakerParameter.Pitch = value_f; 204 | } 205 | } 206 | public const double MinVoicePitch = 0.5; 207 | public const double MaxVoicePitch = 2.0; 208 | 209 | /// 210 | /// 抑揚(0~2) 211 | /// 212 | public double VoiceEmphasis 213 | { 214 | get { return CurrentSpeakerParameter.Range; } 215 | set 216 | { 217 | float value_f = (float)Math.Max(MinVoiceEmphasis, Math.Min(value, MaxVoiceEmphasis)); 218 | IsParameterChanged |= (value_f != CurrentSpeakerParameter.Range); 219 | CurrentSpeakerParameter.Range = value_f; 220 | } 221 | } 222 | public const double MinVoiceEmphasis = 0.0; 223 | public const double MaxVoiceEmphasis = 2.0; 224 | 225 | /// 226 | /// 短ポーズ時間[ms] (80~500)。PauseLong以下。 227 | /// 228 | public int PauseMiddle 229 | { 230 | get { return CurrentSpeakerParameter.PauseMiddle; } 231 | set 232 | { 233 | value = Math.Max(MinPauseMiddle, Math.Min(value, MaxPauseMiddle)); 234 | IsParameterChanged |= (value != CurrentSpeakerParameter.PauseMiddle); 235 | CurrentSpeakerParameter.PauseMiddle = value; 236 | if (PauseLong < value) 237 | { 238 | PauseLong = value; 239 | } 240 | } 241 | } 242 | public const int MinPauseMiddle = 80; 243 | public const int MaxPauseMiddle = 500; 244 | 245 | /// 246 | /// 長ポーズ時間[ms] (100~2000)。PauseMiddle以上。 247 | /// 248 | public int PauseLong 249 | { 250 | get { return CurrentSpeakerParameter.PauseLong; } 251 | set 252 | { 253 | value = Math.Max(MinPauseLong, Math.Min(value, MaxPauseLong)); 254 | IsParameterChanged |= (value != CurrentSpeakerParameter.PauseLong); 255 | CurrentSpeakerParameter.PauseLong = value; 256 | if (value < PauseMiddle) 257 | { 258 | PauseMiddle = value; 259 | } 260 | } 261 | } 262 | public const int MinPauseLong = 100; 263 | public const int MaxPauseLong = 2000; 264 | 265 | /// 266 | /// 文末ポーズ時間[ms] (0~10000) 267 | /// 268 | public int PauseSentence 269 | { 270 | get { return CurrentSpeakerParameter.PauseSentence; } 271 | set 272 | { 273 | value = Math.Max(MinPauseSentence, Math.Min(value, MaxPauseSentence)); 274 | IsParameterChanged |= (value != CurrentSpeakerParameter.PauseSentence); 275 | CurrentSpeakerParameter.PauseSentence = value; 276 | } 277 | } 278 | public const int MinPauseSentence = 0; 279 | public const int MaxPauseSentence = 10000; 280 | 281 | /// 282 | /// ボイスライブラリ名 283 | /// 284 | internal string VoiceDbName; 285 | 286 | /// 287 | /// TTSパラメータ 288 | /// 289 | internal AitalkCore.TtsParam TtsParam; 290 | 291 | /// 292 | /// 話者パラメータのリスト 293 | /// 294 | internal AitalkCore.TtsParam.SpeakerParam[] SpeakerParameters; 295 | 296 | /// 297 | /// 選択されている話者のパラメータ 298 | /// 299 | private AitalkCore.TtsParam.SpeakerParam CurrentSpeakerParameter; 300 | 301 | /// 302 | /// JSONに変換するときに一時的に詰める構造体 303 | /// 304 | [DataContract] 305 | private struct ParameterJson 306 | { 307 | [DataMember] 308 | public string VoiceDbName; 309 | 310 | [DataMember] 311 | public AitalkCore.TtsParam.SpeakerParam[] Speakers; 312 | } 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /Aitalk/source/AitalkWrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Runtime.InteropServices; 6 | using System.Runtime.Serialization; 7 | using System.Runtime.Serialization.Json; 8 | using System.Globalization; 9 | using System.Threading; 10 | using System.IO; 11 | 12 | namespace Aitalk 13 | { 14 | public static class AitalkWrapper 15 | { 16 | /// 17 | /// AITalkを初期化する 18 | /// 19 | /// VOICEROID2のインストールディレクトリ 20 | /// 認証コード 21 | public static void Initialize(string install_directory, string authenticate_code) 22 | { 23 | Finish(); 24 | 25 | // aitalked.dllをロードするために 26 | // DLLの探索パスをVOICEROID2のディレクトリに変更する 27 | if ((InstallDirectory != null) && (InstallDirectory != install_directory)) 28 | { 29 | throw new AitalkException($"インストールディレクトリを変更して再び初期化することはできません。"); 30 | } 31 | InstallDirectory = install_directory; 32 | SetDllDirectory(InstallDirectory); 33 | 34 | // AITalkを初期化する 35 | AitalkCore.Config config; 36 | config.VoiceDbSampleRate = VoiceSampleRate; 37 | config.VoiceDbDirectory = $"{InstallDirectory}\\Voice"; 38 | config.TimeoutMilliseconds = TimeoutMilliseconds; 39 | config.LicensePath = $"{InstallDirectory}\\aitalk.lic"; 40 | config.AuthenticateCodeSeed = authenticate_code; 41 | config.ReservedZero = 0; 42 | var result = AitalkCore.Result.Success; 43 | try 44 | { 45 | result = AitalkCore.Init(ref config); 46 | } 47 | catch (Exception e) 48 | { 49 | throw new AitalkException($"AITalkの初期化に失敗しました。", e); 50 | } 51 | if (result != AitalkCore.Result.Success) 52 | { 53 | throw new AitalkException($"AITalkの初期化に失敗しました。", result); 54 | } 55 | IsInitialized = true; 56 | } 57 | 58 | /// 59 | /// AITalkを終了する 60 | /// 61 | public static void Finish() 62 | { 63 | if (IsInitialized == true) 64 | { 65 | IsInitialized = false; 66 | AitalkCore.End(); 67 | } 68 | CurrentLanguage = null; 69 | CurrentVoice = null; 70 | } 71 | 72 | /// 73 | /// 言語ライブラリの一覧。 74 | /// インストールディレクトリのLangディレクトリの中にあるフォルダ名から生成される。 75 | /// 76 | public static string[] LanguageList 77 | { 78 | get 79 | { 80 | List result = new List(); 81 | try 82 | { 83 | foreach (string path in Directory.GetDirectories($"{InstallDirectory}\\Lang")) 84 | { 85 | result.Add(Path.GetFileName(path)); 86 | } 87 | } 88 | catch (Exception) { } 89 | result.Sort(StringComparer.InvariantCultureIgnoreCase); 90 | return result.ToArray(); 91 | } 92 | } 93 | 94 | /// 95 | /// ボイスライブラリの一覧。 96 | /// インストールディレクトリのVoiceディレクトリの中にあるフォルダ名から生成される。 97 | /// 98 | public static string[] VoiceDbList 99 | { 100 | get 101 | { 102 | List result = new List(); 103 | try 104 | { 105 | foreach (string path in Directory.GetDirectories($"{InstallDirectory}\\Voice")) 106 | { 107 | result.Add(Path.GetFileName(path)); 108 | } 109 | } 110 | catch (Exception) { } 111 | result.Sort(StringComparer.InvariantCultureIgnoreCase); 112 | return result.ToArray(); 113 | } 114 | } 115 | 116 | /// 117 | /// 言語ライブラリを読み込む 118 | /// 119 | /// 言語名 120 | public static void LoadLanguage(string language_name) 121 | { 122 | if (language_name == CurrentLanguage) 123 | { 124 | return; 125 | } 126 | // 言語の設定をする際はカレントディレクトリを一時的にVOICEROID2のインストールディレクトリに変更する 127 | // それ以外ではLangLoad()はエラーを返す 128 | string current_directory = System.IO.Directory.GetCurrentDirectory(); 129 | System.IO.Directory.SetCurrentDirectory(InstallDirectory); 130 | CurrentLanguage = null; 131 | AitalkCore.Result result; 132 | result = AitalkCore.LangClear(); 133 | if ((result == AitalkCore.Result.Success) || (result == AitalkCore.Result.NotLoaded)) 134 | { 135 | result = AitalkCore.LangLoad($"{InstallDirectory}\\Lang\\{language_name}"); 136 | } 137 | System.IO.Directory.SetCurrentDirectory(current_directory); 138 | if (result != AitalkCore.Result.Success) 139 | { 140 | throw new AitalkException($"言語'{language_name}'の読み込みに失敗しました。", result); 141 | } 142 | CurrentLanguage = language_name; 143 | } 144 | 145 | /// 146 | /// フレーズ辞書を読み込む 147 | /// 148 | /// ファイルパス 149 | public static void ReloadPhraseDictionary(string path) 150 | { 151 | AitalkCore.ReloadPhraseDic(null); 152 | if (path == null) 153 | { 154 | return; 155 | } 156 | AitalkCore.Result result; 157 | result = AitalkCore.ReloadPhraseDic(path); 158 | if (result == AitalkCore.Result.UserDictionaryNoEntry) 159 | { 160 | AitalkCore.ReloadPhraseDic(null); 161 | } 162 | else if (result != AitalkCore.Result.Success) 163 | { 164 | throw new AitalkException($"フレーズ辞書'{path}'の読み込みに失敗しました。", result); 165 | } 166 | } 167 | 168 | /// 169 | /// 単語辞書を読み込む 170 | /// 171 | /// ファイルパス 172 | public static void ReloadWordDictionary(string path) 173 | { 174 | AitalkCore.ReloadWordDic(null); 175 | if (path == null) 176 | { 177 | return; 178 | } 179 | AitalkCore.Result result; 180 | result = AitalkCore.ReloadWordDic(path); 181 | if (result == AitalkCore.Result.UserDictionaryNoEntry) 182 | { 183 | AitalkCore.ReloadWordDic(null); 184 | } 185 | else if (result != AitalkCore.Result.Success) 186 | { 187 | throw new AitalkException($"単語辞書'{path}'の読み込みに失敗しました。", result); 188 | } 189 | } 190 | 191 | /// 192 | /// 記号ポーズ辞書を読み込む 193 | /// 194 | /// ファイルパス 195 | public static void ReloadSymbolDictionary(string path) 196 | { 197 | AitalkCore.ReloadSymbolDic(null); 198 | if (path == null) 199 | { 200 | return; 201 | } 202 | AitalkCore.Result result; 203 | result = AitalkCore.ReloadSymbolDic(path); 204 | if (result == AitalkCore.Result.UserDictionaryNoEntry) 205 | { 206 | AitalkCore.ReloadSymbolDic(null); 207 | } 208 | else if (result != AitalkCore.Result.Success) 209 | { 210 | throw new AitalkException($"記号ポーズ辞書'{path}'の読み込みに失敗しました。", result); 211 | } 212 | } 213 | 214 | /// 215 | /// ボイスライブラリを読み込む 216 | /// 217 | /// ボイスライブラリ名 218 | public static void LoadVoice(string voice_db_name) 219 | { 220 | if (voice_db_name == CurrentVoice) 221 | { 222 | return; 223 | } 224 | 225 | CurrentVoice = null; 226 | AitalkCore.VoiceClear(); 227 | if (voice_db_name == null) 228 | { 229 | return; 230 | } 231 | AitalkCore.Result result; 232 | result = AitalkCore.VoiceLoad(voice_db_name); 233 | if (result != AitalkCore.Result.Success) 234 | { 235 | throw new AitalkException($"ボイスライブラリ'{voice_db_name}'の読み込みに失敗しました。", result); 236 | } 237 | 238 | // パラメータを読み込む 239 | GetParameters(out var tts_param, out var speaker_params); 240 | tts_param.TextBufferCallback = TextBufferCallback; 241 | tts_param.RawBufferCallback = RawBufferCallback; 242 | tts_param.TtsEventCallback = TtsEventCallback; 243 | tts_param.PauseBegin = 0; 244 | tts_param.PauseTerm = 0; 245 | tts_param.ExtendFormatFlags = AitalkCore.ExtendFormat.JeitaRuby | AitalkCore.ExtendFormat.AutoBookmark; 246 | Parameter = new AitalkParameter(voice_db_name, tts_param, speaker_params); 247 | 248 | CurrentVoice = voice_db_name; 249 | } 250 | 251 | /// 252 | /// パラメータを取得する 253 | /// 254 | /// パラメータ(話者パラメータを除く) 255 | /// 話者パラメータ 256 | private static void GetParameters(out AitalkCore.TtsParam tts_param, out AitalkCore.TtsParam.SpeakerParam[] speaker_params) 257 | { 258 | // パラメータを格納するのに必要なバッファサイズを取得する 259 | AitalkCore.Result result; 260 | int size = 0; 261 | result = AitalkCore.GetParam(IntPtr.Zero, ref size); 262 | if ((result != AitalkCore.Result.Insufficient) || (size < Marshal.SizeOf())) 263 | { 264 | throw new AitalkException("動作パラメータの長さの取得に失敗しました。", result); 265 | } 266 | 267 | IntPtr ptr = Marshal.AllocCoTaskMem(size); 268 | try 269 | { 270 | // パラメータを読み取る 271 | Marshal.WriteInt32(ptr, (int)Marshal.OffsetOf("Size"), size); 272 | result = AitalkCore.GetParam(ptr, ref size); 273 | if (result != AitalkCore.Result.Success) 274 | { 275 | throw new AitalkException("動作パラメータの取得に失敗しました。", result); 276 | } 277 | tts_param = Marshal.PtrToStructure(ptr); 278 | 279 | // 話者のパラメータを読み取る 280 | speaker_params = new AitalkCore.TtsParam.SpeakerParam[tts_param.NumberOfSpeakers]; 281 | for (int index = 0; index < speaker_params.Length; index++) 282 | { 283 | IntPtr speaker_ptr = IntPtr.Add(ptr, Marshal.SizeOf() + Marshal.SizeOf() * index); 284 | speaker_params[index] = Marshal.PtrToStructure(speaker_ptr); 285 | } 286 | } 287 | finally 288 | { 289 | Marshal.FreeCoTaskMem(ptr); 290 | } 291 | } 292 | 293 | /// 294 | /// パラメータを設定する。 295 | /// param.Sizeおよびparam.NumberOfSpeakersは自動的に設定される。 296 | /// 297 | /// パラメータ(話者パラメータを除く) 298 | /// 話者パラメータ 299 | private static void SetParameters(AitalkCore.TtsParam tts_param, AitalkCore.TtsParam.SpeakerParam[] speaker_params) 300 | { 301 | // パラメータを格納するバッファを確保する 302 | int size = Marshal.SizeOf() + Marshal.SizeOf() * speaker_params.Length; 303 | IntPtr ptr = Marshal.AllocCoTaskMem(size); 304 | try 305 | { 306 | // パラメータを設定する 307 | tts_param.Size = size; 308 | tts_param.NumberOfSpeakers = speaker_params.Length; 309 | Marshal.StructureToPtr(tts_param, ptr, false); 310 | for (int index = 0; index < speaker_params.Length; index++) 311 | { 312 | IntPtr speaker_ptr = IntPtr.Add(ptr, Marshal.SizeOf() + Marshal.SizeOf() * index); 313 | Marshal.StructureToPtr(speaker_params[index], speaker_ptr, false); 314 | } 315 | AitalkCore.Result result; 316 | result = AitalkCore.SetParam(ptr); 317 | if (result != AitalkCore.Result.Success) 318 | { 319 | throw new AitalkException("動作パラメータの設定に失敗しました。", result); 320 | } 321 | } 322 | finally 323 | { 324 | Marshal.FreeCoTaskMem(ptr); 325 | } 326 | } 327 | 328 | /// 329 | /// パラメータが更新されていれば反映する 330 | /// 331 | private static void UpdateParameter() 332 | { 333 | if (Parameter.IsParameterChanged == true) 334 | { 335 | // パラメータを更新する 336 | SetParameters(Parameter.TtsParam, Parameter.SpeakerParameters); 337 | Parameter.IsParameterChanged = false; 338 | } 339 | } 340 | 341 | /// 342 | /// テキストを読み仮名に変換する 343 | /// 344 | /// テキスト 345 | /// タイムアウト[ms]。0以下はタイムアウト無しで待ち続ける。 346 | /// 読み仮名文字列 347 | public static string TextToKana(string text, int timeout = 0) 348 | { 349 | UpdateParameter(); 350 | 351 | // ShiftJISに変換する 352 | UnicodeToShiftJis(text, out byte[] shiftjis_bytes, out int[] shiftjis_positions); 353 | 354 | // コールバックメソッドとの同期オブジェクトを用意する 355 | KanaJobData job_data = new KanaJobData(); 356 | job_data.BufferCapacity = 0x1000; 357 | job_data.Output = new List(); 358 | job_data.CloseEvent = new EventWaitHandle(false, EventResetMode.ManualReset); 359 | GCHandle gc_handle = GCHandle.Alloc(job_data); 360 | try 361 | { 362 | // 変換を開始する 363 | AitalkCore.JobParam job_param; 364 | job_param.ModeInOut = AitalkCore.JobInOut.PlainToKana; 365 | job_param.UserData = GCHandle.ToIntPtr(gc_handle); 366 | AitalkCore.Result result; 367 | result = AitalkCore.TextToKana(out int job_id, ref job_param, shiftjis_bytes); 368 | if (result != AitalkCore.Result.Success) 369 | { 370 | throw new AitalkException($"仮名変換が開始できませんでした。[{string.Join(",", shiftjis_bytes)}]", result); 371 | } 372 | 373 | // 変換の終了を待つ 374 | // timeoutで与えられた時間だけ待つ 375 | bool respond; 376 | respond = job_data.CloseEvent.WaitOne((0 < timeout) ? timeout : -1); 377 | 378 | // 変換を終了する 379 | result = AitalkCore.CloseKana(job_id); 380 | if (respond == false) 381 | { 382 | throw new AitalkException("仮名変換がタイムアウトしました。"); 383 | } 384 | else if (result != AitalkCore.Result.Success) 385 | { 386 | throw new AitalkException("仮名変換が正常に終了しませんでした。", result); 387 | } 388 | } 389 | finally 390 | { 391 | gc_handle.Free(); 392 | } 393 | 394 | // 変換結果に含まれるIrq MARKのバイト位置を文字位置へ置き換える 395 | Encoding encoding = Encoding.GetEncoding(932); 396 | return ReplaceIrqMark(encoding.GetString(job_data.Output.ToArray()), shiftjis_positions); 397 | } 398 | 399 | /// 400 | /// UTF-16からShiftJISに文字列を変換し、文字位置の変換テーブルを生成する。 401 | /// 変換後のShiftJIS文字列と変換テーブルにはヌル終端の分の要素も含まれる。 402 | /// 403 | /// UTF-16文字列 404 | /// ShiftJIS文字列 405 | /// ShiftJISのバイト位置と文字位置の変換テーブル 406 | private static void UnicodeToShiftJis(string unicode_string, out byte[] shiftjis_string, out int[] shiftjis_positions) 407 | { 408 | // 文字位置とUTF-16上でのワード位置の変換テーブルを取得し、 409 | // ShiftJIS上でのバイト位置とUTF-16上でのワード位置の変換テーブルを計算する 410 | Encoding encoding = Encoding.GetEncoding(932); 411 | byte[] shiftjis_string_internal = encoding.GetBytes(unicode_string); 412 | int shiftjis_length = shiftjis_string_internal.Length; 413 | shiftjis_positions = new int[shiftjis_length + 1]; 414 | char[] unicode_char_array = unicode_string.ToArray(); 415 | int[] unicode_indexes = StringInfo.ParseCombiningCharacters(unicode_string); 416 | int char_count = unicode_indexes.Length; 417 | int shiftjis_index = 0; 418 | for (int char_index = 0; char_index < char_count; char_index++) 419 | { 420 | int unicode_index = unicode_indexes[char_index]; 421 | int unicode_count = (((char_index + 1) < char_count) ? unicode_indexes[char_index + 1] : unicode_string.Length) - unicode_index; 422 | int shiftjis_count = encoding.GetByteCount(unicode_char_array, unicode_index, unicode_count); 423 | for (int offset = 0; offset < shiftjis_count; offset++) 424 | { 425 | shiftjis_positions[shiftjis_index + offset] = char_index; 426 | } 427 | shiftjis_index += shiftjis_count; 428 | } 429 | shiftjis_positions[shiftjis_length] = char_count; 430 | 431 | // ヌル終端を付け加える 432 | shiftjis_string = new byte[shiftjis_length + 1]; 433 | Buffer.BlockCopy(shiftjis_string_internal, 0, shiftjis_string, 0, shiftjis_length); 434 | shiftjis_string[shiftjis_length] = 0; 435 | } 436 | 437 | /// 438 | /// Irq MARKによる文節位置を実際の文字位置に置き換える 439 | /// 440 | /// 文字列 441 | /// ShiftJISのバイト位置と文字位置の変換テーブル 442 | /// 変換された文字列 443 | private static string ReplaceIrqMark(string input, int[] shiftjis_positions) 444 | { 445 | StringBuilder output = new StringBuilder(); 446 | int shiftjis_length = shiftjis_positions.Length; 447 | int index = 0; 448 | const string StartOfIrqMark = "(Irq MARK=_AI@"; 449 | const string EndOfIrqMask = ")"; 450 | while (true) 451 | { 452 | int start_pos = input.IndexOf(StartOfIrqMark, index); 453 | if (start_pos < 0) 454 | { 455 | output.Append(input, index, input.Length - index); 456 | break; 457 | } 458 | start_pos += StartOfIrqMark.Length; 459 | output.Append(input, index, start_pos - index); 460 | int end_pos = input.IndexOf(EndOfIrqMask, start_pos); 461 | if (end_pos < 0) 462 | { 463 | output.Append(input, index, input.Length - start_pos); 464 | break; 465 | } 466 | if (int.TryParse(input.Substring(start_pos, end_pos - start_pos), out int shiftjis_index) == false) 467 | { 468 | throw new AitalkException("文節位置の取得に失敗しました。"); 469 | } 470 | if ((shiftjis_index < 0) || (shiftjis_length <= shiftjis_index)) 471 | { 472 | throw new AitalkException("文節位置の特定に失敗しました。"); 473 | } 474 | output.Append(shiftjis_positions[shiftjis_index]); 475 | output.Append(EndOfIrqMask); 476 | index = end_pos + EndOfIrqMask.Length; 477 | } 478 | return output.ToString(); 479 | } 480 | 481 | /// 482 | /// 読み仮名変換時のコールバックメソッド 483 | /// 484 | /// 呼び出し要因 485 | /// ジョブID 486 | /// ユーザーデータ(KanaJobDataへのポインタ) 487 | /// ゼロを返す 488 | private static int TextBufferCallback(AitalkCore.EventReason reason, int job_id, IntPtr user_data) 489 | { 490 | GCHandle gc_handle = GCHandle.FromIntPtr(user_data); 491 | KanaJobData job_data = gc_handle.Target as KanaJobData; 492 | if (job_data == null) 493 | { 494 | return 0; 495 | } 496 | 497 | // 変換できた分だけGetKana()で読み取ってjob_dataのバッファに格納する 498 | int buffer_capacity = job_data.BufferCapacity; 499 | byte[] buffer = new byte[buffer_capacity]; 500 | AitalkCore.Result result; 501 | int read_bytes; 502 | do 503 | { 504 | result = AitalkCore.GetKana(job_id, buffer, buffer_capacity, out read_bytes, out _); 505 | if (result != AitalkCore.Result.Success) 506 | { 507 | break; 508 | } 509 | job_data.Output.AddRange(new ArraySegment(buffer, 0, read_bytes)); 510 | } 511 | while ((buffer_capacity - 1) <= read_bytes); 512 | if (reason == AitalkCore.EventReason.TextBufferClose) 513 | { 514 | job_data.CloseEvent.Set(); 515 | } 516 | return 0; 517 | } 518 | 519 | /// 520 | /// 読み仮名を読み上げてWAVEファイルをストリームに出力する。 521 | /// なお、ストリームへの書き込みは変換がすべて終わった後に行われる。 522 | /// 523 | /// 読み仮名 524 | /// WAVEファイルの出力先ストリーム 525 | /// タイムアウト[ms]。0以下はタイムアウト無しで待ち続ける。 526 | public static void KanaToSpeech(string kana, Stream wave_stream, int timeout = 0) 527 | { 528 | UpdateParameter(); 529 | 530 | // コールバックメソッドとの同期オブジェクトを用意する 531 | SpeechJobData job_data = new SpeechJobData(); 532 | job_data.BufferCapacity = 176400; 533 | job_data.Output = new List(); 534 | job_data.EventData = new List(); 535 | job_data.CloseEvent = new EventWaitHandle(false, EventResetMode.ManualReset); 536 | GCHandle gc_handle = GCHandle.Alloc(job_data); 537 | try 538 | { 539 | // 変換を開始する 540 | AitalkCore.JobParam job_param; 541 | job_param.ModeInOut = AitalkCore.JobInOut.KanaToWave; 542 | job_param.UserData = GCHandle.ToIntPtr(gc_handle); 543 | AitalkCore.Result result; 544 | result = AitalkCore.TextToSpeech(out int job_id, ref job_param, kana); 545 | if (result != AitalkCore.Result.Success) 546 | { 547 | throw new AitalkException("音声変換が開始できませんでした。", result); 548 | } 549 | 550 | // 変換の終了を待つ 551 | // timeoutで与えられた時間だけ待つ 552 | bool respond; 553 | respond = job_data.CloseEvent.WaitOne((0 < timeout) ? timeout : -1); 554 | 555 | // 変換を終了する 556 | result = AitalkCore.CloseSpeech(job_id); 557 | if (respond == false) 558 | { 559 | throw new AitalkException("音声変換がタイムアウトしました。"); 560 | } 561 | else if (result != AitalkCore.Result.Success) 562 | { 563 | throw new AitalkException("音声変換が正常に終了しませんでした。", result); 564 | } 565 | } 566 | finally 567 | { 568 | gc_handle.Free(); 569 | } 570 | 571 | // TTSイベントをJSONに変換する 572 | // 変換後の文字列にヌル終端がてら4の倍数の長さになるようパディングを施す 573 | MemoryStream event_stream = new MemoryStream(); 574 | var serializer = new DataContractJsonSerializer(typeof(List)); 575 | serializer.WriteObject(event_stream, job_data.EventData); 576 | int padding = 4 - ((int)event_stream.Length % 4); 577 | for(int cnt = 0; cnt < padding; cnt++) 578 | { 579 | event_stream.WriteByte(0x0); 580 | } 581 | byte[] event_json = event_stream.ToArray(); 582 | 583 | // データをWAVE形式で出力する 584 | // phonチャンクとしてTTSイベントを埋め込む 585 | byte[] data = job_data.Output.ToArray(); 586 | var writer = new BinaryWriter(wave_stream); 587 | writer.Write(new byte[4] { (byte)'R', (byte)'I', (byte)'F', (byte)'F' }); 588 | writer.Write(44 + event_json.Length + data.Length); 589 | writer.Write(new byte[4] { (byte)'W', (byte)'A', (byte)'V', (byte)'E' }); 590 | writer.Write(new byte[4] { (byte)'f', (byte)'m', (byte)'t', (byte)' ' }); 591 | writer.Write(16); 592 | writer.Write((short)0x1); 593 | writer.Write((short)1); 594 | writer.Write(VoiceSampleRate); 595 | writer.Write(2 * VoiceSampleRate); 596 | writer.Write((short)2); 597 | writer.Write((short)16); 598 | writer.Write(new byte[4] { (byte)'p', (byte)'h', (byte)'o', (byte)'n' }); 599 | writer.Write(event_json.Length); 600 | writer.Write(event_json); 601 | writer.Write(new byte[4] { (byte)'d', (byte)'a', (byte)'t', (byte)'a' }); 602 | writer.Write(data.Length); 603 | writer.Write(data); 604 | } 605 | 606 | /// 607 | /// 音声変換時のデータコールバックメソッド 608 | /// 609 | /// 呼び出し要因 610 | /// ジョブID 611 | /// 時刻[ms] 612 | /// ユーザーデータ(SpeechJobDataへのポインタ) 613 | /// ゼロを返す 614 | private static int RawBufferCallback(AitalkCore.EventReason reason, int job_id, long tick, IntPtr user_data) 615 | { 616 | GCHandle gc_handle = GCHandle.FromIntPtr(user_data); 617 | SpeechJobData job_data = gc_handle.Target as SpeechJobData; 618 | if (job_data == null) 619 | { 620 | return 0; 621 | } 622 | 623 | // 変換できた分だけGetData()で読み取ってjob_dataのバッファに格納する 624 | int buffer_capacity = job_data.BufferCapacity; 625 | byte[] buffer = new byte[2 * buffer_capacity]; 626 | AitalkCore.Result result; 627 | int read_samples; 628 | do 629 | { 630 | result = AitalkCore.GetData(job_id, buffer, buffer_capacity, out read_samples); 631 | if (result != AitalkCore.Result.Success) 632 | { 633 | break; 634 | } 635 | job_data.Output.AddRange(new ArraySegment(buffer, 0, 2 * read_samples)); 636 | } 637 | while ((buffer_capacity - 1) <= read_samples); 638 | if (reason == AitalkCore.EventReason.RawBufferClose) 639 | { 640 | job_data.CloseEvent.Set(); 641 | } 642 | return 0; 643 | } 644 | 645 | /// 646 | /// 音声変換時のイベントコールバックメソッド 647 | /// 648 | /// 呼び出し要因 649 | /// ジョブID 650 | /// 時刻[ms] 651 | /// イベントの値 652 | /// ユーザーデータ(SpeechJobDataへのポインタ) 653 | /// ゼロを返す 654 | private static int TtsEventCallback(AitalkCore.EventReason reason, int job_id, long tick, string name, IntPtr user_data) 655 | { 656 | GCHandle gc_handle = GCHandle.FromIntPtr(user_data); 657 | SpeechJobData job_data = gc_handle.Target as SpeechJobData; 658 | if (job_data == null) 659 | { 660 | return 0; 661 | } 662 | switch (reason) 663 | { 664 | case AitalkCore.EventReason.PhoneticLabel: 665 | case AitalkCore.EventReason.Bookmark: 666 | case AitalkCore.EventReason.AutoBookmark: 667 | job_data.EventData.Add(new TtsEventData(tick, name, reason)); 668 | break; 669 | } 670 | return 0; 671 | } 672 | 673 | /// 674 | /// パラメータ 675 | /// 676 | public static AitalkParameter Parameter { get; private set; } 677 | 678 | /// 679 | /// インストールディレクトリ 680 | /// 681 | public static string InstallDirectory { get; private set; } 682 | 683 | /// 684 | /// 初期化が成功したならtrueを返す 685 | /// 686 | public static bool IsInitialized { get; private set; } = false; 687 | 688 | /// 689 | /// 言語ライブラリが読み込まれているならtrueを返す 690 | /// 691 | public static bool IsLanguageLoaded { get { return CurrentLanguage != null; } } 692 | 693 | /// 694 | /// 読み込まれている言語ライブラリ名 695 | /// 696 | public static string CurrentLanguage { get; private set; } 697 | 698 | /// 699 | /// ボイスライブラリが読み込まれているならtrueを返す 700 | /// 701 | public static bool IsVoiceLoaded { get { return CurrentVoice != null; } } 702 | 703 | /// 704 | /// 読み込まれているボイスライブラリ名 705 | /// 706 | public static string CurrentVoice { get; private set; } 707 | 708 | /// 709 | /// 仮名変換のジョブを管理するクラス 710 | /// 711 | private class KanaJobData 712 | { 713 | public int BufferCapacity; 714 | public List Output; 715 | public EventWaitHandle CloseEvent; 716 | } 717 | 718 | /// 719 | /// 音声変換のジョブを管理するクラス 720 | /// 721 | private class SpeechJobData 722 | { 723 | public int BufferCapacity; 724 | public List Output; 725 | public List EventData; 726 | public EventWaitHandle CloseEvent; 727 | } 728 | 729 | /// 730 | /// TTSイベントのデータを格納する構造体 731 | /// 732 | [DataContract] 733 | public struct TtsEventData 734 | { 735 | [DataMember] 736 | public long Tick; 737 | 738 | [DataMember] 739 | public string Value; 740 | 741 | [DataMember] 742 | public string Type; 743 | 744 | internal TtsEventData(long tick, string value, AitalkCore.EventReason reason) 745 | { 746 | Tick = tick; 747 | Value = value; 748 | switch (reason) 749 | { 750 | case AitalkCore.EventReason.PhoneticLabel: 751 | Type = "Phonetic"; 752 | break; 753 | case AitalkCore.EventReason.Bookmark: 754 | Type = "Bookmark"; 755 | break; 756 | case AitalkCore.EventReason.AutoBookmark: 757 | Type = "AutoBookmark"; 758 | break; 759 | default: 760 | Type = ""; 761 | break; 762 | } 763 | } 764 | } 765 | 766 | /// 767 | /// ボイスライブラリのサンプルレート[Hz] 768 | /// 769 | private const int VoiceSampleRate = 44100; 770 | 771 | /// 772 | /// AITalkのタイムアウト[ms] 773 | /// 774 | private const int TimeoutMilliseconds = 1000; 775 | 776 | [DllImport("Kernel32.dll")] 777 | private static extern bool SetDllDirectory(string lpPathName); 778 | } 779 | 780 | /// 781 | /// AitalkWrapperの例外クラス 782 | /// 783 | [Serializable] 784 | public class AitalkException : Exception 785 | { 786 | public AitalkException() { } 787 | 788 | public AitalkException(string message) 789 | : base(message) { } 790 | 791 | internal AitalkException(string message, AitalkCore.Result result) 792 | : base($"{message}({result})") { } 793 | 794 | public AitalkException(string message, Exception inner) 795 | : base(message, inner) { } 796 | 797 | protected AitalkException(SerializationInfo info, StreamingContext context) 798 | : base(info, context) { } 799 | } 800 | } 801 | -------------------------------------------------------------------------------- /Injecter/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Injecter/Injecter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Diagnostics; 4 | using Codeer.Friendly.Windows; 5 | using Codeer.Friendly.Dynamic; 6 | 7 | namespace Injecter 8 | { 9 | /// 10 | /// VOICEROID2エディタにDLLインジェクションするクラス 11 | /// 12 | public class Injecter 13 | { 14 | /// 15 | /// 認証コードを取得する。 16 | /// VOICEROID2エディタが実行中である必要がある。 17 | /// 18 | /// 認証コードのシード値 19 | public static string GetKey(string process_name) 20 | { 21 | // VOICEROIDエディタのプロセスを検索する 22 | Process[] voiceroid_processes = Process.GetProcessesByName(process_name); 23 | if (voiceroid_processes.Length == 0) 24 | { 25 | return null; 26 | } 27 | Process process = voiceroid_processes[0]; 28 | 29 | // プロセスに接続する 30 | WindowsAppFriend app = new WindowsAppFriend(process); 31 | WindowsAppExpander.LoadAssembly(app, typeof(Injecter).Assembly); 32 | dynamic injected_program = app.Type(typeof(Injecter)); 33 | try 34 | { 35 | // 認証コードを読み取って返す 36 | return injected_program.InjectedGetKey(); 37 | } 38 | catch (Exception) 39 | { 40 | return null; 41 | } 42 | } 43 | 44 | /// 45 | /// 認証コードの取得のためにDLLインジェクション先で実行されるコード 46 | /// 47 | /// 48 | private static string InjectedGetKey() 49 | { 50 | Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); 51 | foreach (Assembly assembly in assemblies) 52 | { 53 | if (assembly.GetName().Name == "AI.Framework.App") 54 | { 55 | Type type = assembly.GetType("AI.Framework.AppFramework"); 56 | var property = type.GetProperty("Current"); 57 | dynamic current = property.GetValue(type); 58 | return (string)current.AppSettings.LicenseKey; 59 | } 60 | } 61 | return null; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Injecter/Injecter.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {4841FCA9-E2DF-4412-8043-F73937D7C2F3} 8 | Exe 9 | Injecter 10 | Injecter 11 | v4.6.1 12 | 512 13 | true 14 | true 15 | false 16 | publish\ 17 | true 18 | Disk 19 | false 20 | Foreground 21 | 7 22 | Days 23 | false 24 | false 25 | true 26 | 1 27 | 1.0.0.%2a 28 | false 29 | true 30 | true 31 | 32 | 33 | x86 34 | true 35 | full 36 | false 37 | bin\Debug\ 38 | DEBUG;TRACE 39 | prompt 40 | 4 41 | 42 | 43 | x86 44 | pdbonly 45 | true 46 | bin\Release\ 47 | TRACE 48 | prompt 49 | 4 50 | 51 | 52 | 7CBCA601675EA137BF833645A92CDE32682325D3 53 | 54 | 55 | Injecter_TemporaryKey.pfx 56 | 57 | 58 | false 59 | 60 | 61 | true 62 | 63 | 64 | LocalIntranet 65 | 66 | 67 | Properties\app.manifest 68 | 69 | 70 | 71 | ..\packages\Codeer.Friendly.2.5.2\lib\net40\Codeer.Friendly.dll 72 | 73 | 74 | ..\packages\Codeer.Friendly.2.5.2\lib\net40\Codeer.Friendly.Dynamic.dll 75 | 76 | 77 | ..\packages\Codeer.Friendly.Windows.2.12.0\lib\net20\Codeer.Friendly.Windows.dll 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | False 101 | Microsoft .NET Framework 4.6.1 %28x86 および x64%29 102 | true 103 | 104 | 105 | False 106 | .NET Framework 3.5 SP1 107 | false 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /Injecter/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Injecter 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | try 11 | { 12 | string exe_name = args[0]; 13 | string process_name = Path.GetFileNameWithoutExtension(exe_name); 14 | string auth_code = Injecter.GetKey(process_name); 15 | Console.Out.Write(auth_code); 16 | Console.Out.Flush(); 17 | } 18 | catch (Exception) { } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Injecter/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // アセンブリに関する一般情報は以下の属性セットをとおして制御されます。 6 | // アセンブリに関連付けられている情報を変更するには、 7 | // これらの属性値を変更してください。 8 | [assembly: AssemblyTitle("Injecter")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Injecter")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // ComVisible を false に設定すると、このアセンブリ内の型は COM コンポーネントから 18 | // 参照できなくなります。COM からこのアセンブリ内の型にアクセスする必要がある場合は、 19 | // その型の ComVisible 属性を true に設定してください。 20 | [assembly: ComVisible(false)] 21 | 22 | // このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります 23 | [assembly: Guid("4841fca9-e2df-4412-8043-f73937d7c2f3")] 24 | 25 | // アセンブリのバージョン情報は次の 4 つの値で構成されています: 26 | // 27 | // メジャー バージョン 28 | // マイナー バージョン 29 | // ビルド番号 30 | // Revision 31 | // 32 | // すべての値を指定するか、次を使用してビルド番号とリビジョン番号を既定に設定できます 33 | // 既定値にすることができます: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /Injecter/Properties/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 48 | 55 | 56 | 70 | -------------------------------------------------------------------------------- /Injecter/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Nkyoku 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # voiceroid_daemon 2 | VOICEROID2のHTTPサーバーデーモン 3 | 4 | [Pythonパッケージ](https://github.com/Nkyoku/pyvcroid2)を作ったのでHTTPサーバーである必要が無かったらそちらのほうが使い勝手が良いと思います。 5 | 6 | ## 概要 7 | VOICEROID2のDLL(aitalked.dll)を直接叩いて、音声データをHTTPで取得できるサーバーソフトです。 8 | よってエディターを起動しておく必要はありません。 9 | ライセンス認証はDLLレベルで行われているため当然ながら動作には有効なライセンスが必要です。 10 | 11 | ## ビルド環境 12 | Visual Studio 2017 13 | 14 | ## 使い方 15 | コマンドプロンプトやPowerShellよりVoiceroidDaemon.exeを起動します。 16 | 初期状態ではポート8080にHTTPサーバーが立ち上がりますので、 17 | ウェブブラウザを使って`http://127.0.0.1:8080/`にアクセスしてください。 18 | 19 | 始めにページ上部のナビゲーションバーから「システムの設定」ページを開き必要な設定事項を入力してください。 20 | aitalked.dllを利用するためにはDLLの認証コードが必要ですが、本ソフトウェアはこれをVOICEROID2エディタから取得することができます。 21 | 「認証コードのシード値」のテキストボックスの下にあるボタンを押すと、取得された文字列がテキストボックスに入力されます。 22 | 待ち受けアドレスを`http://+:8080/`などに設定することで外部からの接続を受け入れることができます。 23 | その場合、管理者権限でコマンドプロンプトやPowerShellを起動して以下のコマンドを入力しアクセスを許可してください。 24 | ``` 25 | netsh http add urlacl url=http://+:8080/ user= 26 | ``` 27 | 設定できたら「保存する」ボタンを押してください。 28 | 29 | 次にナビゲーションバーから「話者の設定」ページを開き、使用するボイスライブラリと話者を選択してください。 30 | ここで表示されるボイスライブラリや話者の名前はキャラクターの日本語名ではなくファイルの名前です。 31 | 設定したら「保存する」ボタンを押してください。 32 | 33 | ここまでダイアログでエラーが表示されなかったなら「Home」ページにて仮名変換や音声変換を試すことができるはずです。 34 | 35 | なお、ここまでの設定はカレントディレクトリに「setting.json」というファイルで保存されます。 36 | しかしVoiceroidDaemonの起動オプションに`/setting=<ファイル名>`と付け加えることでファイル名を変更することができます。 37 | 38 | ## Web API 39 | 仮名変換や音声変換はWeb APIとして呼び出すことが可能です。 40 | 以下にAPIの使用例を示します。 41 | IPアドレスとポートはそれぞれの環境に読み替えてください。 42 | 43 | - 文章をVOICEROIDの読み仮名に変換する① 44 | `http://127.0.0.1:8080/api/converttext/こんにちは` (GETメソッド) 45 | にアクセスすると`(Irq MARK=_AI@5)コ^ンニチワ`というテキストが返ります。 46 | [仮名変換についての詳細はこちら](https://blankalilio.blogspot.com/2019/03/voiceroid2aikana.html) 47 | 48 | - 文章をVOICEROIDの読み仮名に変換する② 49 | `http://127.0.0.1:8080/api/converttext` (POSTメソッド) 50 | に後述する構造のJSONテキストをPOSTしても変換できます。 51 | 52 | - 文章を音声に変換する。 53 | `http://127.0.0.1:8080/api/speechtext/こんばんは` (GETメソッド) 54 | にアクセスするとwavファイルが返ります。 55 | WAVEファイルのフォーマットは44.1kHz,16bit,モノラルです。 56 | 57 | - 文章を音声に変換する。 58 | `http://127.0.0.1:8080/api/speechtext` (POSTメソッド) 59 | に後述する構造のJSONテキストをPOSTしても変換できます。 60 | この場合、文章の代わりに読み仮名を指定して音声変換することもできます。 61 | 62 | ## スピーチパラメータ 63 | 読み仮名変換や音声変換はURLリクエストの形で指定する他、JSON形式のテキストをPOSTすることでも行えます。 64 | 以下にフォーマットを示します。 65 | ``` 66 | { 67 | "Text" : <読み仮名あるいは音声に変換する文章>, 68 | "Kana" : <音声に変換する読み仮名>, 69 | "Speaker" : { 70 | "Volume" : <音量 (0~2)>, 71 | "Speed" : <話速 (0.5~4)>, 72 | "Pitch" : <高さ (0.5~2)>, 73 | "Emphasis" : <抑揚 (0~2)>, 74 | "PauseMiddle" : <短ポーズ時間[ms] (80~500) PauseLong以下>, 75 | "PauseLong" : <長ポーズ時間[ms] (100~2000) PauseMiddle以上>, 76 | "PauseSentence" : <文末ポーズ時間[ms] (0~10000)> 77 | } 78 | } 79 | ``` 80 | 指定しないパラメータは省略できます。その場合、ボイスライブラリの初期値が使用されます。 81 | また、`Volume`, `Speed`, `Pitch`, `Emphasis`はNaNを指定すると初期値が、 82 | `PauseMiddle`, `PauseLong`, `PauseSentence`は-1を指定すると初期値が使用されます。 83 | 84 | ## ライセンス 85 | 本ソフトウェアの製作にあたって以下のライブラリを使用しています。 86 | - [Friendly](https://github.com/Codeer-Software/Friendly) 87 | -------------------------------------------------------------------------------- /VoiceroidDaemon/Controllers/ConvertTextApiController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.AspNetCore.Mvc; 7 | using VoiceroidDaemon.Models; 8 | using Aitalk; 9 | 10 | namespace VoiceroidDaemon.Controllers 11 | { 12 | /// 13 | /// テキストを読み仮名変換するコントローラ 14 | /// 15 | [Route("api/converttext")] 16 | [ApiController] 17 | public class ConvertTextApiController : ControllerBase 18 | { 19 | /// 20 | /// リクエストで与えられたテキストを仮名変換する 21 | /// 22 | /// 仮名変換するテキスト 23 | /// 24 | [HttpGet("{text}")] 25 | public string ConvertTextFromRequest(string text) 26 | { 27 | SpeechModel model = new SpeechModel(); 28 | model.Text = text; 29 | return ConvertTextFromPost(model); 30 | } 31 | 32 | /// 33 | /// POSTで与えられたテキストを仮名変換する 34 | /// 35 | /// 仮名変換するテキストが含まれたパラメータ 36 | /// 37 | [HttpPost] 38 | public string ConvertTextFromPost([FromBody] SpeechModel speech_model) 39 | { 40 | Setting.Lock(); 41 | try 42 | { 43 | if ((speech_model.Text == null) || (speech_model.Text.Length <= 0)) 44 | { 45 | return null; 46 | } 47 | return AitalkWrapper.TextToKana(speech_model.Text, Setting.System.KanaTimeout); 48 | } 49 | catch (Exception) 50 | { 51 | return null; 52 | } 53 | finally 54 | { 55 | Setting.Unlock(); 56 | } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /VoiceroidDaemon/Controllers/GetKeyApiController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Diagnostics; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.AspNetCore.Mvc; 8 | using VoiceroidDaemon.Models; 9 | using System.IO; 10 | using System.Reflection; 11 | 12 | namespace VoiceroidDaemon.Controllers 13 | { 14 | /// 15 | /// テキストを読み仮名変換するコントローラ 16 | /// 17 | [Route("api/getkey")] 18 | [ApiController] 19 | public class GetKeyApiController : ControllerBase 20 | { 21 | /// 22 | /// 起動中のVOICEROID2エディタから認証コードを取得する 23 | /// 24 | /// 25 | [HttpGet("{exe}")] 26 | public string GetKey(string exe) 27 | { 28 | try 29 | { 30 | ProcessStartInfo start_info = new ProcessStartInfo(); 31 | start_info.FileName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\Injecter.exe"; 32 | start_info.Arguments = "\"" + Setting.System.InstallPath + "\\" + (exe ?? Setting.System.VoiceroidEditorExe) + "\""; 33 | start_info.CreateNoWindow = true; 34 | start_info.RedirectStandardOutput = true; 35 | using (Process process = Process.Start(start_info)) 36 | { 37 | process.WaitForExit(10000); 38 | if (process.HasExited == true) 39 | { 40 | string result = process.StandardOutput.ReadToEnd(); 41 | process.WaitForExit(); 42 | return result; 43 | } 44 | else 45 | { 46 | process.Kill(); 47 | } 48 | } 49 | } 50 | catch (Exception) { } 51 | return null; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /VoiceroidDaemon/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.Rendering; 8 | using VoiceroidDaemon.Models; 9 | using Aitalk; 10 | 11 | namespace VoiceroidDaemon.Controllers 12 | { 13 | public class HomeController : Controller 14 | { 15 | [HttpGet] 16 | public IActionResult Index() 17 | { 18 | return View(); 19 | } 20 | 21 | [HttpGet] 22 | public IActionResult SystemSetting() 23 | { 24 | // 言語のリストを作成してビューに渡す 25 | ViewData["LanguageListItems"] = GenerateSelectListItems(AitalkWrapper.LanguageList); 26 | return View(Setting.System); 27 | } 28 | 29 | [HttpPost] 30 | public IActionResult SystemSetting(SystemSettingModel system_setting) 31 | { 32 | if (system_setting != null) 33 | { 34 | string error_message = null; 35 | bool saved = false; 36 | Setting.Lock(); 37 | try 38 | { 39 | error_message = Setting.ApplySystemSetting(system_setting); 40 | saved = Setting.Save(); 41 | } 42 | finally 43 | { 44 | Setting.Unlock(); 45 | } 46 | if (saved == false) 47 | { 48 | ViewData["Alert"] = "設定の保存に失敗しました。"; 49 | } 50 | else if (error_message != null) 51 | { 52 | ViewData["Alert"] = $"設定は保存されましたがエラーが発生しました。{error_message}"; 53 | } 54 | else 55 | { 56 | ViewData["Alert"] = "設定は保存され、設定の有効性が確認されました。\\n一部の設定は再起動するまで反映されません。"; 57 | } 58 | } 59 | return SystemSetting(); 60 | } 61 | 62 | [HttpGet] 63 | public IActionResult SpeakerSetting(string voice_db) 64 | { 65 | SpeakerSettingModel model = Setting.Speaker.Clone(); 66 | model.VoiceDbName = voice_db ?? Setting.Speaker.VoiceDbName; 67 | 68 | // 話者名のリストを取得する 69 | string[] voice_names = null; 70 | Setting.Lock(); 71 | try 72 | { 73 | if ((model.VoiceDbName != null) && (0 < model.VoiceDbName.Length)) 74 | { 75 | AitalkWrapper.LoadVoice(model.VoiceDbName); 76 | voice_names = AitalkWrapper.Parameter.VoiceNames; 77 | } 78 | } 79 | catch (Exception) { } 80 | finally 81 | { 82 | Setting.Unlock(); 83 | } 84 | 85 | // 音声ライブラリと話者のリストを作成してビューに渡す 86 | ViewData["VoiceDbListItems"] = GenerateSelectListItems(AitalkWrapper.VoiceDbList); 87 | ViewData["SpeakerListItems"] = GenerateSelectListItems(voice_names); 88 | return View(model); 89 | } 90 | 91 | [HttpPost] 92 | public IActionResult SpeakerSetting(SpeakerSettingModel speaker_setting) 93 | { 94 | if (speaker_setting != null) 95 | { 96 | string error_message = null; 97 | bool saved = false; 98 | Setting.Lock(); 99 | try 100 | { 101 | error_message = Setting.ApplySpeakerSetting(speaker_setting); 102 | saved = Setting.Save(); 103 | } 104 | finally 105 | { 106 | Setting.Unlock(); 107 | } 108 | if (saved == false) 109 | { 110 | ViewData["Alert"] = "設定の保存に失敗しました。"; 111 | } 112 | else if (error_message != null) 113 | { 114 | ViewData["Alert"] = $"設定は保存されましたがエラーが発生しました。{error_message}"; 115 | } 116 | else 117 | { 118 | ViewData["Alert"] = "設定は保存され、設定の有効性が確認されました。"; 119 | } 120 | } 121 | return SpeakerSetting((string)null); 122 | } 123 | 124 | [HttpGet("/favicon.png")] 125 | public IActionResult Favicon() 126 | { 127 | if (Setting.IconByteArray != null) 128 | { 129 | return new FileContentResult(Setting.IconByteArray, "image/png"); 130 | } 131 | else 132 | { 133 | return new NotFoundResult(); 134 | } 135 | } 136 | 137 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 138 | public IActionResult Error() 139 | { 140 | return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); 141 | } 142 | 143 | private static List GenerateSelectListItems(string[] values) 144 | { 145 | // 選択項目リストを作成する 146 | // 0番目に値がnullなDefault項目を追加する 147 | List result = new List(); 148 | result.Add(new SelectListItem("Default", "")); 149 | if (values != null) 150 | { 151 | result.AddRange(values.Select(name => new SelectListItem(name, name))); 152 | } 153 | return result; 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /VoiceroidDaemon/Controllers/SpeechTextApiController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.AspNetCore.Mvc; 8 | using VoiceroidDaemon.Models; 9 | using Aitalk; 10 | 11 | namespace VoiceroidDaemon.Controllers 12 | { 13 | /// 14 | /// テキストを音声変換するコントローラ 15 | /// 16 | [Route("api/speechtext")] 17 | [ApiController] 18 | public class SpeechTextApiController : ControllerBase 19 | { 20 | /// 21 | /// リクエストで与えられたテキストを音声変換する 22 | /// 23 | /// 読み上げるテキスト 24 | /// 25 | [HttpGet("{text}")] 26 | public IActionResult SpeectTextFromRequest(string text) 27 | { 28 | SpeechModel model = new SpeechModel(); 29 | model.Text = text; 30 | return SpeectTextFromPost(model); 31 | } 32 | 33 | /// 34 | /// POSTで与えられたテキストを音声変換する 35 | /// 36 | /// 読み上げるテキストとパラメータ 37 | /// 38 | [HttpPost] 39 | public IActionResult SpeectTextFromPost([FromBody] SpeechModel speech_model) 40 | { 41 | Setting.Lock(); 42 | try 43 | { 44 | // 話者パラメータを設定する 45 | var speaker = speech_model.Speaker ?? new SpeakerModel(); 46 | AitalkWrapper.Parameter.VoiceVolume = (0 <= speaker.Volume) ? speaker.Volume : Setting.DefaultSpeakerParameter.Volume; 47 | AitalkWrapper.Parameter.VoiceSpeed = (0 <= speaker.Speed) ? speaker.Speed : Setting.DefaultSpeakerParameter.Speed; 48 | AitalkWrapper.Parameter.VoicePitch = (0 <= speaker.Pitch) ? speaker.Pitch : Setting.DefaultSpeakerParameter.Pitch; 49 | AitalkWrapper.Parameter.VoiceEmphasis = (0 <= speaker.Emphasis) ? speaker.Emphasis : Setting.DefaultSpeakerParameter.Emphasis; 50 | AitalkWrapper.Parameter.PauseMiddle = (0 <= speaker.PauseMiddle) ? speaker.PauseMiddle : Setting.DefaultSpeakerParameter.PauseMiddle; 51 | AitalkWrapper.Parameter.PauseLong = (0 <= speaker.PauseLong) ? speaker.PauseLong : Setting.DefaultSpeakerParameter.PauseLong; 52 | AitalkWrapper.Parameter.PauseSentence = (0 <= speaker.PauseSentence) ? speaker.PauseSentence : Setting.DefaultSpeakerParameter.PauseSentence; 53 | 54 | // テキストが与えられた場合は仮名に変換する 55 | string kana = null; 56 | if ((speech_model.Kana != null) && (0 < speech_model.Kana.Length)) 57 | { 58 | kana = speech_model.Kana; 59 | } 60 | else if ((speech_model.Text != null) && (0 < speech_model.Text.Length)) 61 | { 62 | kana = AitalkWrapper.TextToKana(speech_model.Text, Setting.System.KanaTimeout); 63 | } 64 | if ((kana == null) || (kana.Length <= 0)) 65 | { 66 | return new NoContentResult(); 67 | } 68 | 69 | // 音声変換して結果を返す 70 | var wave_stream = new MemoryStream(); 71 | AitalkWrapper.KanaToSpeech(kana, wave_stream, Setting.System.SpeechTimeout); 72 | return new FileContentResult(wave_stream.ToArray(), "audio/wav"); 73 | } 74 | catch(Exception) 75 | { 76 | return new NoContentResult(); 77 | } 78 | finally 79 | { 80 | Setting.Unlock(); 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /VoiceroidDaemon/Models/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace VoiceroidDaemon.Models 4 | { 5 | public class ErrorViewModel 6 | { 7 | public string RequestId { get; set; } 8 | 9 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 10 | } 11 | } -------------------------------------------------------------------------------- /VoiceroidDaemon/Models/SpeakerModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Aitalk; 6 | 7 | namespace VoiceroidDaemon.Models 8 | { 9 | public class SpeakerModel 10 | { 11 | /// 12 | /// 音量(0~2) 13 | /// 14 | public double Volume { get; set; } = double.NaN; 15 | 16 | /// 17 | /// 話速(0.5~4) 18 | /// 19 | public double Speed { get; set; } = double.NaN; 20 | 21 | /// 22 | /// 高さ(0.5~2) 23 | /// 24 | public double Pitch { get; set; } = double.NaN; 25 | 26 | /// 27 | /// 抑揚(0~2) 28 | /// 29 | public double Emphasis { get; set; } = double.NaN; 30 | 31 | /// 32 | /// 短ポーズ時間[ms] (80~500)。PauseLong以下。 33 | /// 34 | public int PauseMiddle { get; set; } = -1; 35 | 36 | /// 37 | /// 長ポーズ時間[ms] (100~2000)。PauseMiddle以上。 38 | /// 39 | public int PauseLong { get; set; } = -1; 40 | 41 | /// 42 | /// 文末ポーズ時間[ms] (0~10000) 43 | /// 44 | public int PauseSentence { get; set; } = -1; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /VoiceroidDaemon/Models/SpeakerSettingModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace VoiceroidDaemon.Models 5 | { 6 | [DataContract] 7 | public class SpeakerSettingModel 8 | { 9 | /// 10 | /// ボイスライブラリ名 11 | /// 12 | [DataMember] 13 | public string VoiceDbName { get; set; } 14 | 15 | /// 16 | /// 話者名。単一話者のライブラリならボイスライブラリ名と同じだと思われる。 17 | /// 18 | [DataMember] 19 | public string SpeakerName { get; set; } 20 | 21 | /// 22 | /// デシリアライズ前に呼ばれる。 23 | /// 初期値を代入する。 24 | /// 25 | /// 26 | [OnDeserializing] 27 | internal void OnDeserializing(StreamingContext context) 28 | { 29 | LoadInitialValues(); 30 | } 31 | 32 | /// 33 | /// コンストラクタ 34 | /// 35 | public SpeakerSettingModel() 36 | { 37 | LoadInitialValues(); 38 | } 39 | 40 | /// 41 | /// メンバーに初期値を代入する 42 | /// 43 | private void LoadInitialValues() 44 | { 45 | VoiceDbName = ""; 46 | SpeakerName = ""; 47 | } 48 | 49 | /// 50 | /// クローンを作成する 51 | /// 52 | /// 53 | public SpeakerSettingModel Clone() 54 | { 55 | return (SpeakerSettingModel)MemberwiseClone(); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /VoiceroidDaemon/Models/SpeechModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace VoiceroidDaemon.Models 7 | { 8 | /// 9 | /// 読み上げパラメータを格納するモデル 10 | /// 11 | public class SpeechModel 12 | { 13 | /// 14 | /// 読み上げるテキスト 15 | /// 16 | public string Text { get; set; } 17 | 18 | /// 19 | /// 読み上げる読み仮名 20 | /// 21 | public string Kana { get; set; } 22 | 23 | /// 24 | /// 話者のパラメータ 25 | /// 26 | public SpeakerModel Speaker { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /VoiceroidDaemon/Models/SystemSettingModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace VoiceroidDaemon.Models 5 | { 6 | [DataContract] 7 | public class SystemSettingModel 8 | { 9 | /// 10 | /// VOICEROID2エディタの実行ファイル名 11 | /// 12 | [DataMember] 13 | public string VoiceroidEditorExe { get; set; } 14 | public static string DefaultVoiceroidEditorExe = "VoiceroidEditor.exe"; 15 | 16 | /// 17 | /// インストールディレクトリのパス 18 | /// 19 | [DataMember] 20 | public string InstallPath { get; set; } 21 | public static string DefaultInstallPath = System.Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + "\\AHS\\VOICEROID2"; 22 | 23 | /// 24 | /// 認証コードのシード値 25 | /// 26 | [DataMember] 27 | public string AuthCodeSeed { get; set; } 28 | 29 | /// 30 | /// 言語名 31 | /// 32 | [DataMember] 33 | public string LanguageName { get; set; } 34 | 35 | /// 36 | /// フレーズ辞書のファイルパス 37 | /// 38 | [DataMember] 39 | public string PhraseDictionaryPath { get; set; } 40 | 41 | /// 42 | /// 単語辞書のファイルパス 43 | /// 44 | [DataMember] 45 | public string WordDictionaryPath { get; set; } 46 | 47 | /// 48 | /// 記号ポーズ辞書のファイルパス 49 | /// 50 | [DataMember] 51 | public string SymbolDictionaryPath { get; set; } 52 | 53 | /// 54 | /// 読み仮名変換のタイムアウト[ms] 55 | /// 56 | [DataMember] 57 | public int KanaTimeout { get; set; } 58 | 59 | /// 60 | /// 音声変換のタイムアウト[ms] 61 | /// 62 | [DataMember] 63 | public int SpeechTimeout { get; set; } 64 | 65 | /// 66 | /// 待ち受けアドレス 67 | /// 68 | [DataMember] 69 | public string ListeningAddress { get; set; } 70 | public static string DefaultListeningAddress = "http://127.0.0.1:8080/"; 71 | 72 | /// 73 | /// デシリアライズ前に呼ばれる。 74 | /// 初期値を代入する。 75 | /// 76 | /// 77 | [OnDeserializing] 78 | internal void OnDeserializing(StreamingContext context) 79 | { 80 | LoadInitialValues(); 81 | } 82 | 83 | /// 84 | /// コンストラクタ 85 | /// 86 | public SystemSettingModel() 87 | { 88 | LoadInitialValues(); 89 | } 90 | 91 | /// 92 | /// メンバーに初期値を代入する 93 | /// 94 | private void LoadInitialValues() 95 | { 96 | VoiceroidEditorExe = DefaultVoiceroidEditorExe; 97 | InstallPath = DefaultInstallPath; 98 | AuthCodeSeed = ""; 99 | LanguageName = "standard"; 100 | PhraseDictionaryPath = ""; 101 | WordDictionaryPath = ""; 102 | SymbolDictionaryPath = ""; 103 | KanaTimeout = 0; 104 | SpeechTimeout = 0; 105 | ListeningAddress = DefaultListeningAddress; 106 | } 107 | 108 | /// 109 | /// クローンを作成する 110 | /// 111 | /// 112 | public SystemSettingModel Clone() 113 | { 114 | return (SystemSettingModel)MemberwiseClone(); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /VoiceroidDaemon/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Diagnostics; 6 | using System.Threading.Tasks; 7 | using System.Text; 8 | using Microsoft.AspNetCore; 9 | using Microsoft.AspNetCore.Hosting; 10 | using Microsoft.Extensions.Configuration; 11 | using Microsoft.Extensions.Logging; 12 | using Aitalk; 13 | 14 | namespace VoiceroidDaemon 15 | { 16 | public class Program 17 | { 18 | public static void Main(string[] args) 19 | { 20 | // Aitalk内でShift-JISの変換を行うため 21 | // .Net Core標準でサポートされないエンコーディングを追加 22 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); 23 | 24 | var config = new ConfigurationBuilder() 25 | .AddCommandLine(args) 26 | .Build(); 27 | 28 | // 設定ファイルを読み込む 29 | string setting_path = config.GetValue("setting"); 30 | if ((setting_path != null) && (0 < setting_path.Length)) 31 | Setting.Path = setting_path; 32 | if (Setting.Load() == false) 33 | { 34 | // 読み出しに失敗したら設定ファイルを新規作成する 35 | if (Setting.Save() == false) 36 | { 37 | // 作成にも失敗したら終了する 38 | throw new IOException("設定ファイルの作成に失敗しました。"); 39 | } 40 | } 41 | 42 | // Webサーバーを構成する 43 | var web_host_builder = WebHost.CreateDefaultBuilder(); 44 | web_host_builder.UseConfiguration(config); 45 | web_host_builder.UseStartup(); 46 | if (0 < Setting.System.ListeningAddress.Length) 47 | { 48 | // 待ち受けURLが指定されていればURLを設定する 49 | web_host_builder.UseUrls(Setting.System.ListeningAddress); 50 | } 51 | var web_host = web_host_builder.Build(); 52 | 53 | // Webサーバーを開始する 54 | web_host.Run(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /VoiceroidDaemon/Setting.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.IO; 7 | using System.Drawing; 8 | using System.Drawing.Imaging; 9 | using System.Runtime.Serialization; 10 | using System.Runtime.Serialization.Json; 11 | using VoiceroidDaemon.Models; 12 | using Aitalk; 13 | 14 | namespace VoiceroidDaemon 15 | { 16 | internal static class Setting 17 | { 18 | /// 19 | /// 標準の設定ファイルパス 20 | /// 21 | public const string DefaultPath = "setting.json"; 22 | 23 | /// 24 | /// 設定ファイルパス 25 | /// 26 | public static string Path = DefaultPath; 27 | 28 | /// 29 | /// システム設定値 30 | /// 31 | public static SystemSettingModel System; 32 | 33 | /// 34 | /// 話者設定値 35 | /// 36 | public static SpeakerSettingModel Speaker; 37 | 38 | /// 39 | /// 話者パラメータの初期値 40 | /// 41 | public static SpeakerModel DefaultSpeakerParameter; 42 | 43 | /// 44 | /// バイナリセマフォ 45 | /// 46 | private static SemaphoreSlim Semaphore = new SemaphoreSlim(1); 47 | 48 | /// 49 | /// セマフォを取得する 50 | /// 51 | public static void Lock() 52 | { 53 | Semaphore.Wait(); 54 | } 55 | 56 | /// 57 | /// セマフォを解放する 58 | /// 59 | public static void Unlock() 60 | { 61 | Semaphore.Release(); 62 | } 63 | 64 | /// 65 | /// VOICEROID2エディタのアイコンファイルのバイト配列 66 | /// 67 | public static byte[] IconByteArray; 68 | 69 | /// 70 | /// 設定ファイルを読み込む 71 | /// 72 | /// 読み込まれたらtrueを返す 73 | public static bool Load() 74 | { 75 | bool result; 76 | SystemSettingModel system_setting; 77 | SpeakerSettingModel speaker_setting; 78 | try 79 | { 80 | using (Stream stream = new FileStream(Path, FileMode.Open, FileAccess.Read)) 81 | { 82 | var serializer = new DataContractJsonSerializer(typeof(SettingJson)); 83 | var setting = (SettingJson)serializer.ReadObject(stream); 84 | system_setting = setting.System; 85 | speaker_setting = setting.Speaker; 86 | } 87 | result = true; 88 | } 89 | catch (Exception) 90 | { 91 | // 初期値を返す 92 | system_setting = new SystemSettingModel(); 93 | speaker_setting = new SpeakerSettingModel(); 94 | result = false; 95 | } 96 | ApplySystemSetting(system_setting); 97 | ApplySpeakerSetting(speaker_setting); 98 | return result; 99 | } 100 | 101 | /// 102 | /// 設定ファイルを保存する 103 | /// 104 | /// 保存できたらtrueを返す 105 | public static bool Save() 106 | { 107 | try 108 | { 109 | using (Stream stream = new FileStream(Path, FileMode.Create, FileAccess.Write)) 110 | { 111 | // 人が読みやすい形でシリアライズするためにインデントと改行を有効にしてCreateJsonWriterを作成する 112 | using (var writer = JsonReaderWriterFactory.CreateJsonWriter(stream, Encoding.UTF8, true, true, " ")) 113 | { 114 | SettingJson setting; 115 | setting.System = System; 116 | setting.Speaker = Speaker; 117 | var serializer = new DataContractJsonSerializer(typeof(SettingJson)); 118 | serializer.WriteObject(writer, setting); 119 | writer.Flush(); 120 | } 121 | } 122 | return true; 123 | } 124 | catch (Exception) 125 | { 126 | return false; 127 | } 128 | } 129 | 130 | /// 131 | /// 新しいシステム設定値を適用する。 132 | /// 133 | /// 新しいシステム設定値 134 | /// エラーメッセージ、もしくはnull 135 | public static string ApplySystemSetting(SystemSettingModel setting) 136 | { 137 | System = setting; 138 | IconByteArray = null; 139 | try 140 | { 141 | // インストールディレクトリと実行ファイルの存在を確認する 142 | if (Directory.Exists(System.InstallPath) == false) 143 | { 144 | return "インストールディレクトリが存在しません。"; 145 | } 146 | string exe_path = $"{System.InstallPath}\\{System.VoiceroidEditorExe}"; 147 | if (File.Exists(exe_path) == false) 148 | { 149 | return "VOICEROID2エディタの実行ファイルが存在しません。"; 150 | } 151 | 152 | // アイコンを取得する 153 | try 154 | { 155 | using (var icon = Icon.ExtractAssociatedIcon(exe_path)) 156 | using (var bitmap = icon.ToBitmap()) 157 | using (var stream = new MemoryStream()) 158 | { 159 | bitmap.Save(stream, ImageFormat.Png); 160 | IconByteArray = stream.ToArray(); 161 | } 162 | } 163 | catch (Exception) { } 164 | 165 | // AITalkを初期化する 166 | AitalkWrapper.Initialize(System.InstallPath, System.AuthCodeSeed); 167 | 168 | // 言語ライブラリを読み込む 169 | if ((System.LanguageName != null) && (0 < System.LanguageName.Length)) 170 | { 171 | // 指定された言語ライブラリを読み込む 172 | AitalkWrapper.LoadLanguage(System.LanguageName); 173 | } 174 | else 175 | { 176 | // 未指定の場合、初めに見つけたものを読み込む 177 | string language_name = AitalkWrapper.LanguageList.FirstOrDefault() ?? ""; 178 | AitalkWrapper.LoadLanguage(language_name); 179 | } 180 | 181 | // フレーズ辞書が指定されていれば読み込む 182 | if (File.Exists(System.PhraseDictionaryPath)) 183 | { 184 | AitalkWrapper.ReloadPhraseDictionary(System.PhraseDictionaryPath); 185 | } 186 | 187 | // 単語辞書が指定されていれば読み込む 188 | if (File.Exists(System.WordDictionaryPath)) 189 | { 190 | AitalkWrapper.ReloadWordDictionary(System.WordDictionaryPath); 191 | } 192 | 193 | // 記号ポーズ辞書が指定されていれば読み込む 194 | if (File.Exists(System.SymbolDictionaryPath)) 195 | { 196 | AitalkWrapper.ReloadSymbolDictionary(System.SymbolDictionaryPath); 197 | } 198 | 199 | return null; 200 | } 201 | catch (AitalkException ex) 202 | { 203 | return ex.Message; 204 | } 205 | catch (Exception ex) 206 | { 207 | return ex.Message; 208 | } 209 | } 210 | 211 | /// 212 | /// 新しい話者設定値を適用する。 213 | /// 214 | /// 新しい話者設定値 215 | /// エラーメッセージ、もしくはnull 216 | public static string ApplySpeakerSetting(SpeakerSettingModel setting) 217 | { 218 | Speaker = setting; 219 | try 220 | { 221 | // ボイスライブラリを読み込む 222 | if (0 < Speaker.VoiceDbName.Length) 223 | { 224 | // 指定されたボイスライブラリを読み込む 225 | string voice_db_name = Speaker.VoiceDbName; 226 | AitalkWrapper.LoadVoice(voice_db_name); 227 | 228 | // 話者が指定されているときはその話者を選択する 229 | if (0 < Speaker.SpeakerName.Length) 230 | { 231 | AitalkWrapper.Parameter.CurrentSpeakerName = Speaker.SpeakerName; 232 | } 233 | } 234 | else 235 | { 236 | // 未指定の場合、初めに見つけたものを読み込む 237 | string voice_db_name = AitalkWrapper.VoiceDbList.FirstOrDefault() ?? ""; 238 | AitalkWrapper.LoadVoice(voice_db_name); 239 | } 240 | 241 | // 話者パラメータの初期値を記憶する 242 | DefaultSpeakerParameter = new SpeakerModel 243 | { 244 | Volume = AitalkWrapper.Parameter.VoiceVolume, 245 | Speed = AitalkWrapper.Parameter.VoiceSpeed, 246 | Pitch = AitalkWrapper.Parameter.VoicePitch, 247 | Emphasis = AitalkWrapper.Parameter.VoiceEmphasis, 248 | PauseMiddle = AitalkWrapper.Parameter.PauseMiddle, 249 | PauseLong = AitalkWrapper.Parameter.PauseLong, 250 | PauseSentence = AitalkWrapper.Parameter.PauseSentence 251 | }; 252 | 253 | return null; 254 | } 255 | catch (AitalkException ex) 256 | { 257 | return ex.Message; 258 | } 259 | catch (Exception ex) 260 | { 261 | return ex.Message; 262 | } 263 | } 264 | 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /VoiceroidDaemon/SettingJson.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | 3 | namespace VoiceroidDaemon 4 | { 5 | [DataContract] 6 | internal struct SettingJson 7 | { 8 | [DataMember] 9 | public Models.SystemSettingModel System; 10 | 11 | [DataMember] 12 | public Models.SpeakerSettingModel Speaker; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /VoiceroidDaemon/SettingValues.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace VoiceroidDaemon 5 | { 6 | /// 7 | /// 設定値を管理するモデル 8 | /// 9 | [DataContract] 10 | public class SettingValues 11 | { 12 | /// 13 | /// VOICEROID2エディタの実行ファイル名 14 | /// 15 | [DataMember] 16 | public string VoiceroidEditorExe; 17 | 18 | /// 19 | /// インストールディレクトリのパス 20 | /// 21 | [DataMember] 22 | public string InstallPath; 23 | 24 | /// 25 | /// 認証コードのシード値 26 | /// 27 | [DataMember] 28 | public string AuthCodeSeed; 29 | 30 | /// 31 | /// 言語名 32 | /// 33 | [DataMember] 34 | public string LanguageName; 35 | 36 | /// 37 | /// フレーズ辞書のファイルパス 38 | /// 39 | [DataMember] 40 | public string PhraseDictionaryPath; 41 | 42 | /// 43 | /// 単語辞書のファイルパス 44 | /// 45 | [DataMember] 46 | public string WordDictionaryPath; 47 | 48 | /// 49 | /// 記号ポーズ辞書のファイルパス 50 | /// 51 | [DataMember] 52 | public string SymbolDictionaryPath; 53 | 54 | /// 55 | /// ボイスライブラリ名 56 | /// 57 | [DataMember] 58 | public string VoiceDbName; 59 | 60 | /// 61 | /// 話者名。単一話者のライブラリならボイスライブラリ名と同じだと思われる。 62 | /// 63 | [DataMember] 64 | public string SpeakerName; 65 | 66 | /// 67 | /// 読み仮名変換のタイムアウト[ms] 68 | /// 69 | [DataMember] 70 | public int KanaTimeout; 71 | 72 | /// 73 | /// 音声変換のタイムアウト[ms] 74 | /// 75 | [DataMember] 76 | public int SpeechTimeout; 77 | 78 | /// 79 | /// 待ち受けアドレス 80 | /// 81 | [DataMember] 82 | public string ListeningAddress; 83 | 84 | /// 85 | /// デシリアライズ前に呼ばれる。 86 | /// 初期値を代入する。 87 | /// 88 | /// 89 | [OnDeserializing] 90 | internal void OnDeserializing(StreamingContext context) 91 | { 92 | LoadInitialValues(); 93 | } 94 | 95 | /// 96 | /// コンストラクタ 97 | /// 98 | public SettingValues() 99 | { 100 | LoadInitialValues(); 101 | } 102 | 103 | /// 104 | /// メンバーに初期値を代入する 105 | /// 106 | private void LoadInitialValues() 107 | { 108 | VoiceroidEditorExe = "VoiceroidEditor.exe"; 109 | InstallPath = System.Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + "\\AHS\\VOICEROID2"; 110 | AuthCodeSeed = ""; 111 | LanguageName = "standard"; 112 | PhraseDictionaryPath = ""; 113 | WordDictionaryPath = ""; 114 | SymbolDictionaryPath = ""; 115 | VoiceDbName = ""; 116 | SpeakerName = ""; 117 | KanaTimeout = 0; 118 | SpeechTimeout = 0; 119 | ListeningAddress = "http://127.0.0.1:8080/"; 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /VoiceroidDaemon/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Diagnostics; 6 | using Microsoft.AspNetCore.Builder; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.AspNetCore.Http; 9 | using Microsoft.AspNetCore.Mvc; 10 | using Microsoft.Extensions.Configuration; 11 | using Microsoft.Extensions.DependencyInjection; 12 | 13 | namespace VoiceroidDaemon 14 | { 15 | public class Startup 16 | { 17 | public Startup(IConfiguration configuration) 18 | { 19 | Configuration = configuration; 20 | } 21 | 22 | public IConfiguration Configuration { get; } 23 | 24 | // This method gets called by the runtime. Use this method to add services to the container. 25 | public void ConfigureServices(IServiceCollection services) 26 | { 27 | // HTMLエンティティを防止する 28 | services.Configure(options => 29 | { 30 | options.TextEncoderSettings = new System.Text.Encodings.Web.TextEncoderSettings(System.Text.Unicode.UnicodeRanges.All); 31 | }); 32 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 33 | } 34 | 35 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 36 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 37 | { 38 | if (env.IsDevelopment()) 39 | { 40 | app.UseDeveloperExceptionPage(); 41 | } 42 | else 43 | { 44 | app.UseExceptionHandler("/Home/Error"); 45 | } 46 | 47 | app.UseStaticFiles(); 48 | 49 | app.UseMvcWithDefaultRoute(); 50 | /*app.UseMvc(routes => 51 | { 52 | routes.MapRoute( 53 | name: "default", 54 | template: "{controller=Home}/{action=Index}/{id?}"); 55 | });*/ 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /VoiceroidDaemon/Views/Home/Contact.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Contact"; 3 | } 4 |

@ViewData["Title"]

5 |

@ViewData["Message"]

6 | 7 |
8 | One Microsoft Way
9 | Redmond, WA 98052-6399
10 | P: 11 | 425.555.0100 12 |
13 | 14 |
15 | Support: Support@example.com
16 | Marketing: Marketing@example.com 17 |
18 | -------------------------------------------------------------------------------- /VoiceroidDaemon/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | @section Head { 5 | 27 | } 28 |

仮名変換

29 |
30 | 31 |
32 | 33 |
34 |
35 |

音声変換

36 |
37 |
38 | 39 |
40 |
41 | -------------------------------------------------------------------------------- /VoiceroidDaemon/Views/Home/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Privacy Policy"; 3 | } 4 |

@ViewData["Title"]

5 | 6 |

Use this page to detail your site's privacy policy.

7 | -------------------------------------------------------------------------------- /VoiceroidDaemon/Views/Home/SpeakerSetting.cshtml: -------------------------------------------------------------------------------- 1 | @model VoiceroidDaemon.Models.SpeakerSettingModel 2 | @{ 3 | ViewData["Title"] = "話者の設定"; 4 | List voice_db_list_items = ViewData["VoiceDbListItems"] as List; 5 | List speaker_list_items = ViewData["SpeakerListItems"] as List; 6 | } 7 | @section Head { 8 | 13 | 31 | } 32 |

@ViewData["Title"]

33 |
34 | @using (Html.BeginForm("SpeakerSetting", "Home", FormMethod.Post)) 35 | { 36 | @Html.LabelFor(model => model.VoiceDbName, "ボイスライブラリ"); 37 | @Html.DropDownListFor(model => model.VoiceDbName, voice_db_list_items, null, new { onChange = "changeVoiceDb()" }); 38 |
39 | @Html.LabelFor(model => model.SpeakerName, "話者"); 40 | @Html.DropDownListFor(model => model.SpeakerName, speaker_list_items); 41 |
42 | 43 | } 44 |
45 | @ViewData["SelectedVoiceDb"] -------------------------------------------------------------------------------- /VoiceroidDaemon/Views/Home/SystemSetting.cshtml: -------------------------------------------------------------------------------- 1 | @model VoiceroidDaemon.Models.SystemSettingModel 2 | @{ 3 | ViewData["Title"] = "システムの設定"; 4 | List language_items = ViewData["LanguageListItems"] as List; 5 | } 6 | @section Head { 7 | 12 | 17 | 44 | } 45 |

@ViewData["Title"]

46 |
47 | @using (Html.BeginForm("SystemSetting", "Home", FormMethod.Post)) 48 | { 49 | @Html.LabelFor(model => model.InstallPath, "インストールディレクトリ (再起動するまで変更は反映されません)"); 50 | @Html.TextBoxFor(model => model.InstallPath, Model.InstallPath, new { placeholder = SystemSettingModel.DefaultInstallPath }); 51 |
52 | @Html.LabelFor(model => model.VoiceroidEditorExe, "VOICEROID2エディタの実行ファイル"); 53 | @Html.TextBoxFor(model => model.VoiceroidEditorExe, Model.VoiceroidEditorExe, new { placeholder = SystemSettingModel.DefaultVoiceroidEditorExe }); 54 |
55 | @Html.LabelFor(model => model.AuthCodeSeed, "認証コードのシード値"); 56 | @Html.TextBoxFor(model => model.AuthCodeSeed, Model.AuthCodeSeed) 57 | 58 |
59 | @Html.LabelFor(model => model.LanguageName, "言語"); 60 | @Html.DropDownListFor(model => model.LanguageName, language_items); 61 |
62 | @Html.LabelFor(model => model.PhraseDictionaryPath, "フレーズ辞書のファイルパス"); 63 | @Html.TextBoxFor(model => model.PhraseDictionaryPath, Model.PhraseDictionaryPath) 64 |
65 | @Html.LabelFor(model => model.WordDictionaryPath, "単語辞書のファイルパス"); 66 | @Html.TextBoxFor(model => model.WordDictionaryPath, Model.WordDictionaryPath) 67 |
68 | @Html.LabelFor(model => model.SymbolDictionaryPath, "記号ポーズ辞書のファイルパス"); 69 | @Html.TextBoxFor(model => model.SymbolDictionaryPath, Model.SymbolDictionaryPath) 70 |
71 | @Html.LabelFor(model => model.KanaTimeout, "読み仮名変換のタイムアウト [ms] (0のとき無制限に待つ)"); 72 | @Html.TextBoxFor(model => model.KanaTimeout, new { value = Model.KanaTimeout, type = "number", min = "0", max = "10000" }) 73 |
74 | @Html.LabelFor(model => model.SpeechTimeout, "音声変換のタイムアウト [ms] (0のとき無制限に待つ)"); 75 | @Html.TextBoxFor(model => model.SpeechTimeout, new { value = Model.SpeechTimeout, type = "number", min = "0", max = "10000" }) 76 |
77 | @Html.LabelFor(model => model.ListeningAddress, "待ち受けアドレス"); 78 | @Html.TextBoxFor(model => model.ListeningAddress, Model.ListeningAddress, new { placeholder = SystemSettingModel.DefaultListeningAddress }) 79 |
80 | 81 | } 82 | -------------------------------------------------------------------------------- /VoiceroidDaemon/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model ErrorViewModel 2 | @{ 3 | ViewData["Title"] = "Error"; 4 | } 5 | 6 |

Error.

7 |

An error occurred while processing your request.

8 | 9 | @if (Model.ShowRequestId) 10 | { 11 |

12 | Request ID: @Model.RequestId 13 |

14 | } 15 | 16 |

Development Mode

17 |

18 | Swapping to Development environment will display more detailed information about the error that occurred. 19 |

20 |

21 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 22 |

23 | -------------------------------------------------------------------------------- /VoiceroidDaemon/Views/Shared/_CookieConsentPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Http.Features 2 | 3 | @{ 4 | var consentFeature = Context.Features.Get(); 5 | var showBanner = !consentFeature?.CanTrack ?? false; 6 | var cookieString = consentFeature?.CreateConsentCookie(); 7 | } 8 | 9 | @if (showBanner) 10 | { 11 | 33 | 41 | } -------------------------------------------------------------------------------- /VoiceroidDaemon/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - VoiceroidDaemon 7 | 8 | 9 | 10 | @RenderSection("Head", required: false) 11 | 12 | 13 | 33 | 34 |
35 | @RenderBody() 36 |
37 | 38 | @RenderSection("Scripts", required: false) 39 | 40 | 41 | -------------------------------------------------------------------------------- /VoiceroidDaemon/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /VoiceroidDaemon/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using VoiceroidDaemon 2 | @using VoiceroidDaemon.Models 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /VoiceroidDaemon/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /VoiceroidDaemon/VoiceroidDaemon.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | AnyCPU 6 | 2.0.1 7 | 8 | 9 | 10 | false 11 | AnyCPU 12 | 13 | 14 | 15 | false 16 | AnyCPU 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /VoiceroidDaemon/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /VoiceroidDaemon/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /VoiceroidDaemon/wwwroot/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} 6 | /*# sourceMappingURL=bootstrap-theme.min.css.map */ -------------------------------------------------------------------------------- /VoiceroidDaemon/wwwroot/css/site.min.css: -------------------------------------------------------------------------------- 1 | body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}#qrCode{margin:15px}@media screen and (max-width:767px){.carousel-caption{display:none}} -------------------------------------------------------------------------------- /voiceroid_daemon.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.168 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "voiceroidd", "voiceroidd\voiceroidd.csproj", "{59726917-207F-4788-A149-6147E725CB85}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aitalk", "Aitalk\Aitalk.csproj", "{B78D299F-CBB0-486F-A333-4013854480A7}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VoiceroidDaemon", "VoiceroidDaemon\VoiceroidDaemon.csproj", "{AF74AC19-E106-426B-B784-3EEF41EA3B29}" 11 | ProjectSection(ProjectDependencies) = postProject 12 | {4841FCA9-E2DF-4412-8043-F73937D7C2F3} = {4841FCA9-E2DF-4412-8043-F73937D7C2F3} 13 | EndProjectSection 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Injecter", "Injecter\Injecter.csproj", "{4841FCA9-E2DF-4412-8043-F73937D7C2F3}" 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {59726917-207F-4788-A149-6147E725CB85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {59726917-207F-4788-A149-6147E725CB85}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {59726917-207F-4788-A149-6147E725CB85}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {59726917-207F-4788-A149-6147E725CB85}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {B78D299F-CBB0-486F-A333-4013854480A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {B78D299F-CBB0-486F-A333-4013854480A7}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {B78D299F-CBB0-486F-A333-4013854480A7}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {B78D299F-CBB0-486F-A333-4013854480A7}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {AF74AC19-E106-426B-B784-3EEF41EA3B29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {AF74AC19-E106-426B-B784-3EEF41EA3B29}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {AF74AC19-E106-426B-B784-3EEF41EA3B29}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {AF74AC19-E106-426B-B784-3EEF41EA3B29}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {4841FCA9-E2DF-4412-8043-F73937D7C2F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {4841FCA9-E2DF-4412-8043-F73937D7C2F3}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {4841FCA9-E2DF-4412-8043-F73937D7C2F3}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {4841FCA9-E2DF-4412-8043-F73937D7C2F3}.Release|Any CPU.Build.0 = Release|Any CPU 39 | EndGlobalSection 40 | GlobalSection(SolutionProperties) = preSolution 41 | HideSolutionNode = FALSE 42 | EndGlobalSection 43 | GlobalSection(ExtensibilityGlobals) = postSolution 44 | SolutionGuid = {290AF929-DF9E-4D42-BE30-E4EA4676695B} 45 | EndGlobalSection 46 | EndGlobal 47 | -------------------------------------------------------------------------------- /voiceroidd/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /voiceroidd/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.IO; 4 | using System.Runtime.InteropServices; 5 | using System.Runtime.Serialization; 6 | using System.Runtime.Serialization.Json; 7 | 8 | namespace VoiceroidDaemon 9 | { 10 | [DataContract] 11 | internal class Configuration 12 | { 13 | /// 14 | /// VOICEROID2エディタの実行ファイル名 15 | /// 16 | [DataMember] 17 | public string VoiceroidEditorExe; 18 | 19 | /// 20 | /// インストールディレクトリのパス 21 | /// 22 | [DataMember] 23 | public string InstallPath; 24 | 25 | /// 26 | /// 認証コードのシード値 27 | /// 28 | [DataMember] 29 | public string AuthCodeSeed = ""; 30 | 31 | /// 32 | /// 言語名 33 | /// 34 | [DataMember] 35 | public string LanguageName; 36 | 37 | /// 38 | /// フレーズ辞書のファイルパス 39 | /// 40 | [DataMember] 41 | public string PhraseDictionaryPath = ""; 42 | 43 | /// 44 | /// 単語辞書のファイルパス 45 | /// 46 | [DataMember] 47 | public string WordDictionaryPath = ""; 48 | 49 | /// 50 | /// 記号ポーズ辞書のファイルパス 51 | /// 52 | [DataMember] 53 | public string SymbolDictionaryPath = ""; 54 | 55 | /// 56 | /// ボイスライブラリ名 57 | /// 58 | [DataMember] 59 | public string VoiceDbName = ""; 60 | 61 | /// 62 | /// 話者名。単一話者のライブラリならボイスライブラリ名と同じだと思われる。 63 | /// 64 | [DataMember] 65 | public string VoiceName = ""; 66 | 67 | /// 68 | /// 読み仮名変換のタイムアウト[ms] 69 | /// 70 | [DataMember] 71 | public int KanaTimeout = 0; 72 | 73 | /// 74 | /// 音声変換のタイムアウト[ms] 75 | /// 76 | [DataMember] 77 | public int SpeechTimeout = 0; 78 | 79 | /// 80 | /// 待ち受けアドレス 81 | /// 82 | [DataMember] 83 | public string ListeningAddress; 84 | 85 | /// 86 | /// デシリアライズ前に呼ばれる。 87 | /// 初期値を代入する。 88 | /// 89 | /// 90 | [OnDeserializing] 91 | internal void OnDeserializing(StreamingContext context) 92 | { 93 | LoadInitialValues(); 94 | } 95 | 96 | /// 97 | /// コンストラクタ 98 | /// 99 | private Configuration() { } 100 | 101 | /// 102 | /// 既定の初期値でないメンバーに初期値を代入する 103 | /// 104 | private void LoadInitialValues() 105 | { 106 | VoiceroidEditorExe = "VoiceroidEditor.exe"; 107 | InstallPath = System.Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + "\\AHS\\VOICEROID2"; 108 | LanguageName = "standard"; 109 | ListeningAddress = "http://127.0.0.1:8080/"; 110 | } 111 | 112 | /// 113 | /// 設定ファイルを読み込む 114 | /// 115 | /// 設定ファイルのパス 116 | /// 読み込まれた設定 117 | public static Configuration Load(string file_path, out bool not_exists) 118 | { 119 | not_exists = !File.Exists(file_path); 120 | try 121 | { 122 | using (Stream stream = new FileStream(file_path, FileMode.Open, FileAccess.Read)) 123 | { 124 | var serializer = new DataContractJsonSerializer(typeof(Configuration)); 125 | return (Configuration)serializer.ReadObject(stream); 126 | } 127 | } 128 | catch (Exception) 129 | { 130 | // 初期値を返す 131 | var config = new Configuration(); 132 | config.LoadInitialValues(); 133 | return config; 134 | } 135 | } 136 | 137 | /// 138 | /// 設定ファイルを保存する 139 | /// 140 | /// 設定ファイルのパス 141 | /// 保存できたらtrueを返す 142 | public bool Save(string file_path) 143 | { 144 | try 145 | { 146 | using (Stream stream = new FileStream(file_path, FileMode.Create, FileAccess.Write)) 147 | { 148 | using (var writer = JsonReaderWriterFactory.CreateJsonWriter(stream, Encoding.UTF8, true, true, " ")) 149 | { 150 | var serializer = new DataContractJsonSerializer(typeof(Configuration)); 151 | serializer.WriteObject(writer, this); 152 | writer.Flush(); 153 | } 154 | } 155 | return true; 156 | } 157 | catch (Exception) 158 | { 159 | return false; 160 | } 161 | } 162 | } 163 | 164 | internal static class IniFileHandler 165 | { 166 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] 167 | public static extern uint GetPrivateProfileString( 168 | string lpAppName, 169 | string lpKeyName, 170 | string lpDefault, 171 | StringBuilder lpReturnedString, 172 | uint nSize, 173 | string lpFileName); 174 | 175 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] 176 | public static extern uint WritePrivateProfileString( 177 | string lpAppName, 178 | string lpKeyName, 179 | string lpString, 180 | string lpFileName); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /voiceroidd/Injecter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Diagnostics; 4 | using Codeer.Friendly.Windows; 5 | using Codeer.Friendly.Dynamic; 6 | 7 | namespace VoiceroidDaemon 8 | { 9 | /// 10 | /// VOICEROID2エディタにDLLインジェクションするクラス 11 | /// 12 | public class Injecter 13 | { 14 | /// 15 | /// 認証コードを取得する。 16 | /// VOICEROID2エディタが実行中である必要がある。 17 | /// 18 | /// 認証コードのシード値 19 | public static string GetKey() 20 | { 21 | // VOICEROIDエディタのプロセスを検索する 22 | Process[] voiceroid_processes = Process.GetProcessesByName("VoiceroidEditor"); 23 | if (voiceroid_processes.Length == 0) 24 | { 25 | return null; 26 | } 27 | Process process = voiceroid_processes[0]; 28 | 29 | // プロセスに接続する 30 | WindowsAppFriend app = new WindowsAppFriend(process); 31 | WindowsAppExpander.LoadAssembly(app, typeof(Injecter).Assembly); 32 | dynamic injected_program = app.Type(typeof(Injecter)); 33 | try 34 | { 35 | // 認証コードを読み取って返す 36 | return injected_program.InjectedGetKey(); 37 | } 38 | catch (Exception) 39 | { 40 | return null; 41 | } 42 | } 43 | 44 | /// 45 | /// 認証コードの取得のためにDLLインジェクション先で実行されるコード 46 | /// 47 | /// 48 | private static string InjectedGetKey() 49 | { 50 | Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); 51 | foreach (Assembly assembly in assemblies) 52 | { 53 | if (assembly.GetName().Name == "AI.Framework.App") 54 | { 55 | Type type = assembly.GetType("AI.Framework.AppFramework"); 56 | var property = type.GetProperty("Current"); 57 | dynamic current = property.GetValue(type); 58 | return (string)current.AppSettings.LicenseKey; 59 | } 60 | } 61 | return null; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /voiceroidd/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.IO; 4 | using System.Net; 5 | using System.Web; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using System.Windows.Forms; 9 | using System.Drawing; 10 | using System.Diagnostics; 11 | using System.Runtime.Serialization.Json; 12 | using System.ComponentModel.DataAnnotations; 13 | using McMaster.Extensions.CommandLineUtils; 14 | using Aitalk; 15 | 16 | namespace VoiceroidDaemon 17 | { 18 | [Command(Name = "voiceroidd", Description = "VOICEROID2 HTTP Server Daemon", ThrowOnUnexpectedArgument = false)] 19 | [SuppressDefaultHelpOption] 20 | class Program 21 | { 22 | /// 23 | /// エントリーポイント 24 | /// 25 | /// コマンドライン引数 26 | public static int Main(string[] args) 27 | { 28 | // コマンドライン引数をパースしてOnExecute()を呼び出す 29 | return CommandLineApplication.Execute(args); 30 | } 31 | 32 | /// 33 | /// 設定ファイルのパス 34 | /// 35 | [Option("-c", CommandOptionType.SingleValue)] 36 | private string ConfigFilePath { get; } = "config.json"; 37 | 38 | /// 39 | /// 動作モード。 40 | /// "server"ならサーバー、"auth"なら認証コードの取得 41 | /// 42 | [Argument(0, Description = "auth or server")] 43 | public string OperationMode { get; } = ""; 44 | 45 | /// 46 | /// プログラムの実行する 47 | /// 48 | private void OnExecute() 49 | { 50 | // 設定を読み込む 51 | Config = Configuration.Load(ConfigFilePath, out bool not_exists); 52 | if (not_exists == true) 53 | { 54 | Config.Save(ConfigFilePath); 55 | } 56 | 57 | try 58 | { 59 | // 動作モードに応じて処理を開始する 60 | switch (OperationMode.ToLower()) 61 | { 62 | case "auth": 63 | GetAuthCode(); 64 | break; 65 | 66 | case "server": 67 | StartServer(); 68 | break; 69 | 70 | default: 71 | // ヘルプテキストを表示する 72 | MessageBox.Show( 73 | $@"コマンド 74 | ・認証コードを取得する。 75 | voiceroidd auth 76 | ・HTTPサーバーを起動する。 77 | voiceroidd server 78 | 79 | オプション 80 | ・設定ファイルのパスを明示的に指定する。未指定の場合は'config.json'が使用される。 81 | -c " 82 | , Caption); 83 | break; 84 | } 85 | } 86 | catch(Exception ex) 87 | { 88 | MessageBox.Show(ex.ToString(), Caption, MessageBoxButtons.OK, MessageBoxIcon.Error); 89 | } 90 | } 91 | 92 | /// 93 | /// 認証コードを取得して設定ファイルに記録するモード 94 | /// 95 | private void GetAuthCode() 96 | { 97 | DialogResult result; 98 | result = MessageBox.Show( 99 | "DLLの初期化に必要な認証コードをVOICEROID2エディタから取得します。\nVOICEROID2エディタを起動してください。", 100 | Caption, MessageBoxButtons.OKCancel); 101 | if (result != DialogResult.OK) 102 | { 103 | MessageBox.Show("認証コードの取得は中断されました。", Caption); 104 | } 105 | else 106 | { 107 | string auth_code_seed = Injecter.GetKey(); 108 | if (auth_code_seed == null) 109 | { 110 | MessageBox.Show("認証コードの取得に失敗しました。", Caption); 111 | } 112 | else 113 | { 114 | result = MessageBox.Show( 115 | $"認証コード'{auth_code_seed}'を取得しました。\n設定を{ConfigFilePath}に保存しますか?", 116 | Caption, MessageBoxButtons.YesNo); 117 | if (result != DialogResult.Yes) 118 | { 119 | MessageBox.Show("認証コードは保存されませんでした。", Caption); 120 | } 121 | else 122 | { 123 | Config.AuthCodeSeed = auth_code_seed; 124 | if (Config.Save(ConfigFilePath) == true) 125 | { 126 | MessageBox.Show("設定を保存しました。", Caption); 127 | } 128 | else 129 | { 130 | MessageBox.Show("設定の保存に失敗しました。", Caption); 131 | } 132 | } 133 | } 134 | } 135 | } 136 | 137 | /// 138 | /// HTTPサーバーを起動するモード 139 | /// 140 | private void StartServer() 141 | { 142 | #if DEBUG 143 | // Debugビルドの場合、ログファイルを出力する 144 | Trace.Listeners.Add(new TextWriterTraceListener("trace.log")); 145 | Trace.AutoFlush = true; 146 | #endif 147 | 148 | // AITalkを初期化する 149 | AitalkWrapper.Initialize(Config.InstallPath, Config.AuthCodeSeed); 150 | 151 | try 152 | { 153 | // 言語ライブラリを読み込む 154 | AitalkWrapper.LoadLanguage(Config.LanguageName); 155 | 156 | // フレーズ辞書が設定されていれば読み込む 157 | if (File.Exists(Config.PhraseDictionaryPath)) 158 | { 159 | AitalkWrapper.ReloadPhraseDictionary(Config.PhraseDictionaryPath); 160 | } 161 | 162 | // 単語辞書が設定されていれば読み込む 163 | if (File.Exists(Config.WordDictionaryPath)) 164 | { 165 | AitalkWrapper.ReloadWordDictionary(Config.WordDictionaryPath); 166 | } 167 | 168 | // 記号ポーズ辞書が設定されていれば読み込む 169 | if (File.Exists(Config.SymbolDictionaryPath)) 170 | { 171 | AitalkWrapper.ReloadSymbolDictionary(Config.SymbolDictionaryPath); 172 | } 173 | 174 | // ボイスライブラリを読み込む 175 | AitalkWrapper.LoadVoice(Config.VoiceDbName); 176 | 177 | // 話者を設定する 178 | AitalkWrapper.Parameter.CurrentSpeakerName = Config.VoiceName; 179 | 180 | // 処理を別スレッドで実行する 181 | Task task = Task.Factory.StartNew(Run); 182 | 183 | // トレイアイコンを作成する 184 | // アイコンはVOICEROIDエディタのものを使用するが、ダメならこの実行ファイルのものを使用する 185 | NotifyIcon notify_icon = new NotifyIcon(); 186 | try 187 | { 188 | notify_icon.Icon = Icon.ExtractAssociatedIcon($"{Config.InstallPath}\\{Config.VoiceroidEditorExe}"); 189 | } 190 | catch (Exception) 191 | { 192 | notify_icon.Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath); 193 | } 194 | notify_icon.Text = $"{Caption} : {Config.VoiceName}\nListening at {Config.ListeningAddress}"; 195 | notify_icon.Visible = true; 196 | 197 | // トレイアイコンのコンテキストメニューを作成する 198 | ContextMenu menu = new ContextMenu(); 199 | menu.MenuItems.Add(new MenuItem("Exit", new EventHandler((object sender, EventArgs e) => 200 | { 201 | StopServerCancelToken.Cancel(); 202 | task.Wait(); 203 | notify_icon.Visible = false; 204 | Application.Exit(); 205 | Environment.Exit(1); 206 | }))); 207 | notify_icon.ContextMenu = menu; 208 | 209 | // メッセージループを開始する 210 | Application.Run(); 211 | } 212 | finally 213 | { 214 | AitalkWrapper.Finish(); 215 | } 216 | } 217 | 218 | // 処理を本体 219 | private void Run() 220 | { 221 | try 222 | { 223 | // HTTPサーバーを開始する 224 | var server = new HttpListener(); 225 | server.Prefixes.Add(Config.ListeningAddress); 226 | server.Start(); 227 | Trace.WriteLine($"HTTP server is listening at {Config.ListeningAddress}"); 228 | Task server_task = WaitConnections(server, StopServerCancelToken.Token); 229 | try 230 | { 231 | server_task.Wait(); 232 | } 233 | catch (Exception ex) 234 | { 235 | Trace.WriteLine($"{DateTime.Now} : server_task.Wait()\n{ex}"); 236 | } 237 | } 238 | catch (Exception ex) 239 | { 240 | Trace.WriteLine($"{DateTime.Now} : Run()\n{ex}"); 241 | MessageBox.Show(ex.ToString(), Caption); 242 | return; 243 | } 244 | } 245 | 246 | // 接続を待ち受ける 247 | private async Task WaitConnections(HttpListener server, CancellationToken cancel_token) 248 | { 249 | cancel_token.Register(() => server.Stop()); 250 | while (cancel_token.IsCancellationRequested == false) 251 | { 252 | // リクエストを取得する 253 | var context = await server.GetContextAsync(); 254 | try 255 | { 256 | ProcessRequest(context); 257 | } 258 | catch (Exception ex) 259 | { 260 | Trace.WriteLine($"{DateTime.Now} : WaitConnections()\n{ex}"); 261 | MessageBox.Show(ex.ToString(), Caption); 262 | return; 263 | } 264 | } 265 | } 266 | 267 | // リクエストを処理する 268 | private void ProcessRequest(HttpListenerContext context) 269 | { 270 | HttpListenerRequest request = context.Request; 271 | HttpListenerResponse responce = context.Response; 272 | 273 | try 274 | { 275 | if (request.HttpMethod != "GET") 276 | { 277 | throw new NotImplementedException(); 278 | } 279 | 280 | int offset = 0; 281 | string path = request.RawUrl.Substring(1); 282 | var query = HttpUtility.ParseQueryString(request.Url.Query); 283 | 284 | // メソッド名を調べる 285 | if (UrlMatch(path, "kana/", ref offset) == true) 286 | { 287 | // 仮名変換メソッドを呼び出している 288 | if (UrlMatch(path, "fromtext/", ref offset) == false) 289 | { 290 | throw new ArgumentException("変換するテキストが指定されていません。"); 291 | } 292 | 293 | // 変換するテキストを取得する 294 | string text_encoded = path.Substring(offset, path.Length - offset); 295 | string text = HttpUtility.UrlDecode(text_encoded); 296 | string kana = AitalkWrapper.TextToKana(text, Config.KanaTimeout); 297 | 298 | // 仮名を返す 299 | byte[] result = Encoding.UTF8.GetBytes(kana); 300 | responce.OutputStream.Write(result, 0, result.Length); 301 | responce.ContentEncoding = Encoding.UTF8; 302 | responce.ContentType = "text/plain"; 303 | } 304 | else if (UrlMatch(path, "speech/", ref offset) == true) 305 | { 306 | // 音声変換メソッドを呼び出している 307 | string kana = null; 308 | if (UrlMatch(path, "fromtext/", ref offset) == true) 309 | { 310 | // テキストが入力されたときは仮名に変換する 311 | string text_encoded = path.Substring(offset, path.Length - offset); 312 | string text = HttpUtility.UrlDecode(text_encoded); 313 | kana = AitalkWrapper.TextToKana(text, Config.KanaTimeout); 314 | } 315 | else if (UrlMatch(path, "fromkana/", ref offset) == true) 316 | { 317 | string kana_encoded = path.Substring(offset, path.Length - offset); 318 | kana = HttpUtility.UrlDecode(kana_encoded); 319 | } 320 | else 321 | { 322 | throw new ArgumentException("変換するテキストが指定されていません。"); 323 | } 324 | 325 | // 音声に変換する 326 | var stream = new MemoryStream(); 327 | AitalkWrapper.KanaToSpeech(kana, stream, Config.SpeechTimeout); 328 | 329 | // 音声を返す 330 | byte[] result = stream.ToArray(); 331 | responce.OutputStream.Write(result, 0, result.Length); 332 | responce.ContentType = "audio/wav"; 333 | } 334 | else if (path == "voicedb.json") 335 | { 336 | // ボイスライブラリの一覧を返す 337 | string[] voice_db_list = AitalkWrapper.VoiceDbList; 338 | using (var stream = new MemoryStream()) 339 | { 340 | var serializer = new DataContractJsonSerializer(typeof(string[])); 341 | serializer.WriteObject(stream, voice_db_list); 342 | byte[] result = stream.ToArray(); 343 | responce.OutputStream.Write(result, 0, result.Length); 344 | responce.ContentEncoding = Encoding.UTF8; 345 | responce.ContentType = "application/json"; 346 | } 347 | } 348 | else if (path == "param.json") 349 | { 350 | // TTSパラメータを返す 351 | byte[] result = AitalkWrapper.Parameter.ToJson(); 352 | responce.OutputStream.Write(result, 0, result.Length); 353 | responce.ContentEncoding = Encoding.UTF8; 354 | responce.ContentType = "application/json"; 355 | } 356 | else 357 | { 358 | throw new FileNotFoundException(); 359 | } 360 | } 361 | catch(NotImplementedException) 362 | { 363 | responce.StatusCode = (int)HttpStatusCode.NotImplemented; 364 | } 365 | catch (FileNotFoundException) 366 | { 367 | responce.StatusCode = (int)HttpStatusCode.NotFound; 368 | } 369 | catch (Exception ex) 370 | { 371 | // 例外を文字列化して返す 372 | responce.StatusCode = (int)HttpStatusCode.InternalServerError; 373 | byte[] byte_data = Encoding.UTF8.GetBytes(ex.ToString()); 374 | responce.OutputStream.Write(byte_data, 0, byte_data.Length); 375 | responce.ContentEncoding = Encoding.UTF8; 376 | responce.ContentType = "text/plain"; 377 | } 378 | 379 | // レスポンスを返す 380 | responce.Close(); 381 | } 382 | 383 | // subpathがurlのパスの一部に一致するか調べ、一致したら次の部分パスを比較できるようにインデックスをずらす 384 | private static bool UrlMatch(string url, string subpath, ref int offset) 385 | { 386 | if (string.Compare(url, offset, subpath, 0, subpath.Length) == 0) 387 | { 388 | offset += subpath.Length; 389 | return true; 390 | } 391 | else 392 | { 393 | return false; 394 | } 395 | } 396 | 397 | /// 398 | /// 設定 399 | /// 400 | private Configuration Config; 401 | 402 | // サーバーを終了させるためのCancellationToken 403 | private CancellationTokenSource StopServerCancelToken = new CancellationTokenSource(); 404 | 405 | /// 406 | /// メッセージボックスなどのキャプション 407 | /// 408 | private const string Caption = "Voiceroid Daemon"; 409 | } 410 | } 411 | -------------------------------------------------------------------------------- /voiceroidd/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // アセンブリに関する一般情報は以下の属性セットをとおして制御されます。 6 | // アセンブリに関連付けられている情報を変更するには、 7 | // これらの属性値を変更してください。 8 | [assembly: AssemblyTitle("voiceroidd")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("voiceroidd")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // ComVisible を false に設定すると、このアセンブリ内の型は COM コンポーネントから 18 | // 参照できなくなります。COM からこのアセンブリ内の型にアクセスする必要がある場合は、 19 | // その型の ComVisible 属性を true に設定してください。 20 | [assembly: ComVisible(false)] 21 | 22 | // このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります 23 | [assembly: Guid("59726917-207f-4788-a149-6147e725cb85")] 24 | 25 | // アセンブリのバージョン情報は次の 4 つの値で構成されています: 26 | // 27 | // メジャー バージョン 28 | // マイナー バージョン 29 | // ビルド番号 30 | // Revision 31 | // 32 | // すべての値を指定するか、次を使用してビルド番号とリビジョン番号を既定に設定できます 33 | // 既定値にすることができます: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /voiceroidd/app.manifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 53 | 54 | true 55 | 56 | 57 | 58 | 59 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /voiceroidd/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /voiceroidd/voiceroidd.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {59726917-207F-4788-A149-6147E725CB85} 8 | WinExe 9 | voiceroidd 10 | voiceroidd 11 | v4.6.1 12 | 512 13 | true 14 | true 15 | publish\ 16 | true 17 | Disk 18 | false 19 | Foreground 20 | 7 21 | Days 22 | false 23 | false 24 | true 25 | 0 26 | 1.0.0.%2a 27 | false 28 | false 29 | true 30 | 31 | 32 | 33 | AnyCPU 34 | true 35 | full 36 | false 37 | ..\Debug\ 38 | DEBUG;TRACE 39 | prompt 40 | 4 41 | 42 | 43 | AnyCPU 44 | pdbonly 45 | true 46 | ..\Release\ 47 | TRACE 48 | prompt 49 | 4 50 | 51 | 52 | 53 | 54 | 55 | app.manifest 56 | 57 | 58 | 59 | ..\packages\Codeer.Friendly.2.5.2\lib\net40\Codeer.Friendly.dll 60 | 61 | 62 | ..\packages\Codeer.Friendly.2.5.2\lib\net40\Codeer.Friendly.Dynamic.dll 63 | 64 | 65 | ..\packages\Codeer.Friendly.Windows.2.12.0\lib\net20\Codeer.Friendly.Windows.dll 66 | 67 | 68 | ..\packages\McMaster.Extensions.CommandLineUtils.2.3.2\lib\net45\McMaster.Extensions.CommandLineUtils.dll 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | ..\packages\System.ValueTuple.4.4.0\lib\net47\System.ValueTuple.dll 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | False 100 | Microsoft .NET Framework 4.6.1 %28x86 および x64%29 101 | true 102 | 103 | 104 | False 105 | .NET Framework 3.5 SP1 106 | false 107 | 108 | 109 | 110 | 111 | {b78d299f-cbb0-486f-a333-4013854480a7} 112 | Aitalk 113 | 114 | 115 | 116 | --------------------------------------------------------------------------------