diff --git a/OpenUtau.Core/Classic/ExeResampler.cs b/OpenUtau.Core/Classic/ExeResampler.cs index e17557513..14a2ce89c 100644 --- a/OpenUtau.Core/Classic/ExeResampler.cs +++ b/OpenUtau.Core/Classic/ExeResampler.cs @@ -3,13 +3,11 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; -using System.Text; using System.Threading; using NAudio.Wave; using OpenUtau.Core; using OpenUtau.Core.Format; using OpenUtau.Core.Util; -using OpenUtau.Core.Ustx; using Serilog; namespace OpenUtau.Classic { @@ -102,10 +100,17 @@ public string DoResamplerReturnsFile(ResamplerItem args, ILogger logger) { string ArgParam = FormattableString.Invariant( $"\"{args.inputTemp}\" \"{tmpFile}\" {MusicMath.GetToneName(args.tone)} {args.velocity} \"{args.GetFlagsString()}\" {args.offset} {args.durRequired} {args.consonant} {args.cutoff} {args.volume} {args.modulation} !{args.tempo} {Base64.Base64EncodeInt12(args.pitches)}"); logger.Information($" > [thread-{threadId}] {FilePath} {ArgParam}"); + string resamplerOutput; if (useWine) { - ProcessRunner.Run(winePath, $"{FilePath} {ArgParam}", logger); + resamplerOutput = ProcessRunner.Run(winePath, $"{FilePath} {ArgParam}", logger); } else { - ProcessRunner.Run(FilePath, ArgParam, logger); + resamplerOutput = ProcessRunner.Run(FilePath, ArgParam, logger); + } + // check if the file has been created + if (!File.Exists(tmpFile)) { + throw new Core.Render.ResamplerFailedException( + $" > [thread-{threadId}]\n{resamplerOutput}" + ); } return tmpFile; } diff --git a/OpenUtau.Core/Render/IRenderer.cs b/OpenUtau.Core/Render/IRenderer.cs index a1d420cc1..eeed8365b 100644 --- a/OpenUtau.Core/Render/IRenderer.cs +++ b/OpenUtau.Core/Render/IRenderer.cs @@ -7,6 +7,9 @@ namespace OpenUtau.Core.Render { public class NoResamplerException : Exception { } public class NoWavtoolException : Exception { } + public class ResamplerFailedException : Exception { + public ResamplerFailedException(string message) : base(message) {} + } /// /// Render result of a phrase. diff --git a/OpenUtau.Core/Render/RenderEngine.cs b/OpenUtau.Core/Render/RenderEngine.cs index 75bc3d35b..e97a3ae4f 100644 --- a/OpenUtau.Core/Render/RenderEngine.cs +++ b/OpenUtau.Core/Render/RenderEngine.cs @@ -120,6 +120,9 @@ public Tuple> RenderMixdown(TaskScheduler uiScheduler, ref } else if (innerEx.Any(e => e is DllNotFoundException)) { DocManager.Inst.ExecuteCmd(new ErrorMessageNotification( new MessageCustomizableException("Failed to render.", ": ", flatEx))); + } else if (innerEx.Any(e => e is ResamplerFailedException)) { + DocManager.Inst.ExecuteCmd(new ErrorMessageNotification( + new MessageCustomizableException("Failed to render.", "", flatEx))); } else { DocManager.Inst.ExecuteCmd(new ErrorMessageNotification( new MessageCustomizableException("Failed to render.", "", flatEx))); diff --git a/OpenUtau.Core/Util/ProcessRunner.cs b/OpenUtau.Core/Util/ProcessRunner.cs index b599de30d..c30956da7 100644 --- a/OpenUtau.Core/Util/ProcessRunner.cs +++ b/OpenUtau.Core/Util/ProcessRunner.cs @@ -1,57 +1,94 @@ using System; using System.Diagnostics; using System.IO; +using System.Text; using System.Threading; using Serilog; namespace OpenUtau.Core.Util { public static class ProcessRunner { public static bool DebugSwitch { get; set; } - public static void Run(string file, string args, ILogger logger, string workDir = null, int timeoutMs = 60000) { + public static string Run(string file, string args, ILogger logger, string workDir = null, int timeoutMs = 60000) { if (!File.Exists(file)) { throw new FileNotFoundException($"Executable {file} not found."); } var threadId = Thread.CurrentThread.ManagedThreadId; + var output = new StringBuilder(); + var outputLock = new object(); + + // Signals used to ensure the async stdout/stderr readers have flushed all data before we read `output`. + using var stdoutDone = new ManualResetEventSlim(false); + using var stderrDone = new ManualResetEventSlim(false); using (var proc = new Process()) { proc.StartInfo = new ProcessStartInfo(file, args) { - Environment = {{"LANG", "ja_JP.utf8"}}, + Environment = { { "LANG", "ja_JP.utf8" } }, UseShellExecute = false, - RedirectStandardOutput = DebugSwitch, + RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, WorkingDirectory = workDir, }; - if (DebugSwitch) { - proc.OutputDataReceived += (o, e) => { - if (!string.IsNullOrEmpty(e.Data)) { - logger.Information($"ProcessRunner >>> [thread-{threadId}] {e.Data}"); - } - }; - } + + proc.OutputDataReceived += (o, e) => { + if (e.Data == null) { + stdoutDone.Set(); + return; + } + if (DebugSwitch) { + logger.Information($"ProcessRunner >>> [thread-{threadId}] {e.Data}"); + } + lock (outputLock) { + output.AppendLine(e.Data); + } + }; proc.ErrorDataReceived += (o, e) => { - if (!string.IsNullOrEmpty(e.Data)) { - logger.Error($"ProcessRunner >>> [thread-{threadId}] {e.Data}"); + if (e.Data == null) { + stderrDone.Set(); + return; + } + logger.Error($"ProcessRunner >>> [thread-{threadId}] {e.Data}"); + lock (outputLock) { + output.AppendLine(e.Data); } }; + proc.Start(); - if (DebugSwitch) { - proc.BeginOutputReadLine(); - } + proc.BeginOutputReadLine(); proc.BeginErrorReadLine(); + + bool exited; if (timeoutMs <= 0) { proc.WaitForExit(); + exited = true; } else { - if (proc.WaitForExit(timeoutMs)) { - return; - } + exited = proc.WaitForExit(timeoutMs); + } + + if (!exited) { logger.Warning($"ProcessRunner >>> [thread-{threadId}] Timeout, killing..."); try { - proc.Kill(); + proc.Kill(entireProcessTree: true); logger.Warning($"ProcessRunner >>> [thread-{threadId}] Killed."); } catch (Exception e) { logger.Error(e, $"ProcessRunner >>> [thread-{threadId}] Failed to kill"); } } + + try { + proc.WaitForExit(); + } catch { /* process already disposed/exited */ } + + stdoutDone.Wait(TimeSpan.FromSeconds(5)); + stderrDone.Wait(TimeSpan.FromSeconds(5)); + + lock (outputLock) { + if (!exited) { + output.AppendLine("Killed due to timeout."); + } else { + output.Append("Exit code ").Append(proc.ExitCode); + } + return output.ToString(); + } } } } diff --git a/OpenUtau/Strings/Strings.axaml b/OpenUtau/Strings/Strings.axaml index bfc779b45..1a96c6438 100644 --- a/OpenUtau/Strings/Strings.axaml +++ b/OpenUtau/Strings/Strings.axaml @@ -198,6 +198,7 @@ Do you want to continue by splitting at the nearest position after current playh Try installing the latest Visual C++ Redistributable. https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170 Character not allowed in regular expression - regular expression error - + Resampler failed to create output file(s). Resampler errors like this are often caused by oto.ini configuration issues. Consider generating a singer error report to check for such issues. Installing "{0}"... To use exe resamplers or wavtools on Linux: diff --git a/OpenUtau/Strings/Strings.de-DE.axaml b/OpenUtau/Strings/Strings.de-DE.axaml index 3910a43c0..71aa73200 100644 --- a/OpenUtau/Strings/Strings.de-DE.axaml +++ b/OpenUtau/Strings/Strings.de-DE.axaml @@ -100,6 +100,7 @@ + Der Resampler konnte keine Ausgabedatei(en) erzeugen. Resampler-Fehler wie dieser werden oft von oto.ini-Konfigurationsfehlern ausgelöst. Generieren Sie einen Sänger-Fehlerbericht, um derartige Fehler zu finden.