/// <summary> /// AviUtl拡張編集ファイルのドロップ処理を行う。 /// </summary> /// <param name="exoFilePath">AviUtl拡張編集ファイルパス。</param> /// <param name="exoLength">AviUtl拡張編集オブジェクトのフレーム数。</param> /// <param name="layer">ドロップ先レイヤー番号。</param> /// <param name="aviUtlFileDropService"> /// AviUtl拡張編集ファイルドロップサービス。 /// </param> /// <returns>警告文字列。成功したならば null 。</returns> private static async Task <string> DoOperateExoDrop( string exoFilePath, int exoLength, int layer, IAviUtlFileDropService aviUtlFileDropService) { var result = GcmzDrops.FileDrop.Result.Fail; try { result = await aviUtlFileDropService.Run( exoFilePath, exoLength, layer, ExoDropTimeoutMilliseconds); } catch (Exception ex) { ThreadTrace.WriteException(ex); result = GcmzDrops.FileDrop.Result.Fail; } return(MakeFailMessageFromExoDropResult(result, true)); }
/// <summary> /// コンストラクタ。 /// </summary> /// <param name="canExecuteSource">コマンド実施可否状態のプッシュ通知。</param> /// <param name="processGetter">VOICEROIDプロセス取得デリゲート。</param> /// <param name="talkTextReplaceConfigGetter"> /// トークテキスト置換設定取得デリゲート。 /// </param> /// <param name="exoConfigGetter"> /// AviUtl拡張編集ファイル用設定取得デリゲート。 /// </param> /// <param name="appConfigGetter">アプリ設定取得デリゲート。</param> /// <param name="talkTextGetter">トークテキスト取得デリゲート。</param> /// <param name="aviUtlFileDropService"> /// AviUtl拡張編集ファイルドロップサービス。 /// </param> public AsyncSaveCommandHolder( IObservable <bool> canExecuteSource, Func <IProcess> processGetter, Func <TalkTextReplaceConfig> talkTextReplaceConfigGetter, Func <ExoConfig> exoConfigGetter, Func <AppConfig> appConfigGetter, Func <string> talkTextGetter, IAviUtlFileDropService aviUtlFileDropService) : base(canExecuteSource) { ValidateArgumentNull(processGetter, nameof(processGetter)); ValidateArgumentNull( talkTextReplaceConfigGetter, nameof(talkTextReplaceConfigGetter)); ValidateArgumentNull(exoConfigGetter, nameof(exoConfigGetter)); ValidateArgumentNull(appConfigGetter, nameof(appConfigGetter)); ValidateArgumentNull(talkTextGetter, nameof(talkTextGetter)); ValidateArgumentNull(aviUtlFileDropService, nameof(aviUtlFileDropService)); this.ParameterMaker = () => new CommandParameter( processGetter(), talkTextReplaceConfigGetter(), exoConfigGetter(), appConfigGetter(), talkTextGetter()); this.AviUtlFileDropService = aviUtlFileDropService; }
/// <summary> /// コンストラクタ。 /// </summary> /// <param name="processes">VOICEROIDプロセスコレクション。</param> /// <param name="canUseConfig">各設定値を利用可能な状態であるか否か。</param> /// <param name="talkTextReplaceConfig">トークテキスト置換設定値。</param> /// <param name="exoConfig">AviUtl拡張編集ファイル用設定値。</param> /// <param name="appConfig">アプリ設定値。</param> /// <param name="uiConfig">UI設定値。</param> /// <param name="lastStatus">直近のアプリ状態値の設定先。</param> /// <param name="canModifyNotifier"> /// 設定変更可能な状態であるか否かの設定先。 /// </param> /// <param name="windowActivateService">ウィンドウアクティブ化サービス。</param> /// <param name="voiceroidActionService"> /// VOICEROIDプロセスアクションサービス。 /// </param> /// <param name="aviUtlFileDropService"> /// AviUtl拡張編集ファイルドロップサービス。 /// </param> public VoiceroidViewModel( IReadOnlyCollection <IProcess> processes, IReadOnlyReactiveProperty <bool> canUseConfig, IReadOnlyReactiveProperty <TalkTextReplaceConfig> talkTextReplaceConfig, IReadOnlyReactiveProperty <ExoConfig> exoConfig, IReadOnlyReactiveProperty <AppConfig> appConfig, IReadOnlyReactiveProperty <UIConfig> uiConfig, IReactiveProperty <IAppStatus> lastStatus, IReactiveProperty <bool> canModifyNotifier, IWindowActivateService windowActivateService, IVoiceroidActionService voiceroidActionService, IAviUtlFileDropService aviUtlFileDropService) { ValidateArgumentNull(processes, nameof(processes)); ValidateArgumentNull(canUseConfig, nameof(canUseConfig)); ValidateArgumentNull(talkTextReplaceConfig, nameof(talkTextReplaceConfig)); ValidateArgumentNull(exoConfig, nameof(exoConfig)); ValidateArgumentNull(appConfig, nameof(appConfig)); ValidateArgumentNull(uiConfig, nameof(uiConfig)); ValidateArgumentNull(lastStatus, nameof(lastStatus)); ValidateArgumentNull(canModifyNotifier, nameof(canModifyNotifier)); ValidateArgumentNull(windowActivateService, nameof(windowActivateService)); ValidateArgumentNull(voiceroidActionService, nameof(voiceroidActionService)); ValidateArgumentNull(aviUtlFileDropService, nameof(aviUtlFileDropService)); this.LastStatus = lastStatus; this.WindowActivateService = windowActivateService; this.VoiceroidActionService = voiceroidActionService; this.IsTextClearing = this.MakeInnerPropertyOf(appConfig, c => c.IsTextClearing); this.VoiceroidExecutablePathes = this.MakeInnerPropertyOf( uiConfig, c => c.VoiceroidExecutablePathes, notifyOnSameValue: true); // 表示状態のVOICEROIDプロセスコレクション this.VisibleProcesses = appConfig .ObserveInnerProperty(c => c.VoiceroidVisibilities) .Select(vv => vv.SelectVisibleOf(processes)) .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); // VOICEROID選択コマンド実行可能状態 // 表示状態のVOICEROIDプロセスが2つ以上あれば選択可能 this.IsSelectVoiceroidCommandExecutable = this.VisibleProcesses .Select(vp => vp.Count >= 2) .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); // VOICEROID選択コマンドのチップテキスト this.SelectVoiceroidCommandTip = this.VisibleProcesses .Select(_ => this.MakeSelectVoiceroidCommandTip()) .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); // 最適表示列数 // 6プロセス単位で列数を増やす this.VisibleProcessesColumnCount = this.VisibleProcesses .Select(vp => Math.Min(Math.Max(1, (vp.Count + 5) / 6), 3)) .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); // 選択中VOICEROIDプロセス this.SelectedProcess = new ReactiveProperty <IProcess>(this.VisibleProcesses.Value.First()) .AddTo(this.CompositeDisposable); // UI設定周りのセットアップ this.SetupUIConfig(uiConfig, processes); // 選択プロセス状態 this.IsProcessStartup = this .ObserveSelectedProcessProperty(p => p.IsStartup) .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); this.IsProcessRunning = this .ObserveSelectedProcessProperty(p => p.IsRunning) .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); this.IsProcessPlaying = this .ObserveSelectedProcessProperty(p => p.IsPlaying) .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); this.IsProcessExecutable = Observable .CombineLatest( this.SelectedProcess, uiConfig.ObserveInnerProperty(c => c.VoiceroidExecutablePathes), (p, pathes) => { var path = (p == null) ? null : pathes[p.Id]?.Path; return(!string.IsNullOrEmpty(path) && File.Exists(path)); }) .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); var processSaving = this .ObserveSelectedProcessProperty(p => p.IsSaving) .DistinctUntilChanged(); var processDialogShowing = this .ObserveSelectedProcessProperty(p => p.IsDialogShowing) .DistinctUntilChanged(); // トークテキスト this.TalkText = new ReactiveProperty <string>("").AddTo(this.CompositeDisposable); this.TalkTextLengthLimit = new ReactiveProperty <int>(TextComponent.TextLengthLimit) .AddTo(this.CompositeDisposable); this.IsTalkTextTabAccepted = this.MakeInnerPropertyOf(appConfig, c => c.IsTabAccepted); // アイドル状態設定先 var idle = new ReactiveProperty <bool>(true).AddTo(this.CompositeDisposable); this.IsIdle = idle; // 音声保存コマンド処理中フラグ設定先 var saveCommandBusy = new ReactiveProperty <bool>(false).AddTo(this.CompositeDisposable); // トークテキストファイルドロップコマンド処理中フラグ設定先 var dropTalkTextFileCommandBusy = new ReactiveProperty <bool>(false).AddTo(this.CompositeDisposable); // 本体側のテキストを使う設定 this.UseTargetText = this.MakeInnerPropertyOf( appConfig, c => c.UseTargetText, new[] { canUseConfig, this.IsIdle } .CombineLatestValuesAreAllTrue() .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable)); // - 本体側のテキストを使う設定が有効 // または // - 音声保存処理中かつ保存成功時クリア設定が有効 // ならばトークテキスト編集不可 this.IsTalkTextEditable = new[] { this.UseTargetText, new[] { saveCommandBusy, this.IsTextClearing } .CombineLatestValuesAreAllTrue() .DistinctUntilChanged(), } .CombineLatest(flags => flags.Any(f => f)) .Inverse() .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); // VOICEROID選択コマンドコレクション(要素数 10 固定) this.SelectVoiceroidCommands = new ReadOnlyCollection <ICommand>( Enumerable.Range(0, 10) .Select( index => this.MakeCommand( () => this.ExecuteSelectVoiceroidCommand(index), this.IsSelectVoiceroidCommandExecutable, this.VisibleProcesses .Select(vp => index < vp.Count) .DistinctUntilChanged())) .ToArray()); // 前方/後方VOICEROID選択コマンド this.SelectPreviousVoiceroidCommand = this.MakeCommand( this.ExecuteSelectPreviousVoiceroidCommand, this.IsSelectVoiceroidCommandExecutable); this.SelectNextVoiceroidCommand = this.MakeCommand( this.ExecuteSelectNextVoiceroidCommand, this.IsSelectVoiceroidCommandExecutable); // 起動/終了コマンド this.RunExitCommand = this.MakeAsyncCommand( this.ExecuteRunExitCommand, canUseConfig, this.IsIdle, this.IsProcessStartup.Inverse(), processSaving.Inverse(), processDialogShowing.Inverse()); // 再生/停止コマンド var playStopCommandHolder = new AsyncPlayStopCommandHolder( new[] { canUseConfig, this.IsIdle, this.IsProcessRunning, processSaving.Inverse(), processDialogShowing.Inverse(), dropTalkTextFileCommandBusy.Inverse(), } .CombineLatestValuesAreAllTrue() .DistinctUntilChanged() .ObserveOnUIDispatcher(), () => this.SelectedProcess.Value, () => talkTextReplaceConfig.Value.VoiceReplaceItems, () => this.TalkText.Value, () => appConfig.Value.UseTargetText); this.PlayStopCommand = playStopCommandHolder.Command; playStopCommandHolder.Result .Where(r => r != null) .Subscribe(async r => await this.OnPlayStopCommandExecuted(r)) .AddTo(this.CompositeDisposable); // 保存コマンド var saveCommandHolder = new AsyncSaveCommandHolder( new[] { canUseConfig, this.IsIdle, this.IsProcessRunning, processSaving.Inverse(), processDialogShowing.Inverse(), new[] { this.TalkText .Select(t => !string.IsNullOrWhiteSpace(t)) .DistinctUntilChanged(), this.UseTargetText, // 敢えて空白文を保存したいことはまず無いと思われるので、 // 誤クリック抑止の意味も込めて空白文は送信不可とする。 // ただし本体側の文章を使う場合は空白文でも保存可能とする。 // ↓のコメントを外すと(可能ならば)空白文送信保存可能になる。 //this // .ObserveSelectedProcessProperty(p => p.CanSaveBlankText) // .DistinctUntilChanged(), } .CombineLatest(flags => flags.Any(f => f)) .DistinctUntilChanged(), dropTalkTextFileCommandBusy.Inverse(), } .CombineLatestValuesAreAllTrue() .DistinctUntilChanged() .ObserveOnUIDispatcher(), () => this.SelectedProcess.Value, () => talkTextReplaceConfig.Value, () => exoConfig.Value, () => appConfig.Value, () => this.TalkText.Value, aviUtlFileDropService); this.SaveCommand = saveCommandHolder.Command; saveCommandHolder.IsBusy .Subscribe(f => saveCommandBusy.Value = f) .AddTo(this.CompositeDisposable); saveCommandHolder.Result .Where(r => r != null) .Subscribe(async r => await this.OnSaveCommandExecuted(r)) .AddTo(this.CompositeDisposable); // 再生も音声保存もしていない時をアイドル状態とみなす new[] { playStopCommandHolder.IsBusy, saveCommandHolder.IsBusy, } .CombineLatestValuesAreAllFalse() .DistinctUntilChanged() .Subscribe( f => { idle.Value = f; // アイドル状態なら設定変更可能とする canModifyNotifier.Value = f; }) .AddTo(this.CompositeDisposable); // トークテキスト用ファイルドラッグオーバーコマンド this.DragOverTalkTextFileCommand = this.MakeCommand <DragEventArgs>( this.ExecuteDragOverTalkTextFileCommand, this.IsTalkTextEditable); // トークテキスト用ファイルドロップコマンド var dropTalkTextFileCommandHolder = new AsyncCommandHolder <DragEventArgs>( this.IsTalkTextEditable, this.ExecuteDropTalkTextFileCommand); this.DropTalkTextFileCommand = dropTalkTextFileCommandHolder.Command; dropTalkTextFileCommandHolder.IsBusy .Subscribe(f => dropTalkTextFileCommandBusy.Value = f) .AddTo(this.CompositeDisposable); }
/// <summary> /// 設定を基にAviUtl拡張編集ファイル関連の処理を行う。 /// </summary> /// <param name="filePath">WAVEファイルパス。</param> /// <param name="voiceroidId">VOICEROID識別ID。</param> /// <param name="text">テキスト。</param> /// <param name="appConfig">アプリ設定。</param> /// <param name="exoConfig">AviUtl拡張編集ファイル用設定。</param> /// <param name="aviUtlFileDropService"> /// AviUtl拡張編集ファイルドロップサービス。 /// </param> /// <returns>処理結果とエラー文字列のタプル。</returns> private static async Task <Tuple <ExoOperationResult, string> > DoOperateExo( string filePath, VoiceroidId voiceroidId, string text, AppConfig appConfig, ExoConfig exoConfig, IAviUtlFileDropService aviUtlFileDropService) { if (appConfig.IsExoFileMaking) { var exoFilePath = Path.ChangeExtension(filePath, @".exo"); // 共通設定更新 ExoCommonConfig common = null; var gcmzResult = GcmzDrops.FileDrop.Result.Success; if ( appConfig.IsSavedExoFileToAviUtl && appConfig.IsExoFileParamReplacedByAviUtl) { common = exoConfig.Common.Clone(); gcmzResult = UpdateExoCommonConfigByAviUtl(ref common); } // ファイル保存 var exo = await DoOperateExoSave( exoFilePath, filePath, text, common ?? exoConfig.Common, exoConfig.CharaStyles[voiceroidId]); if (exo == null) { return (Tuple.Create( ExoOperationResult.SaveFail, @".exo ファイルを保存できませんでした。")); } // ファイルドロップ if (appConfig.IsSavedExoFileToAviUtl) { // UpdateExoCommonConfigByAviUtl で失敗しているなら実施しない var failMessage = (gcmzResult != GcmzDrops.FileDrop.Result.Success) ? MakeFailMessageFromExoDropResult(gcmzResult) : await DoOperateExoDrop( exoFilePath, exo.Length, appConfig.AviUtlDropLayers[voiceroidId].Layer, aviUtlFileDropService); // 処理失敗時、そもそも AviUtl が起動していないなら成功扱い if (failMessage != null && Process.GetProcessesByName(@"aviutl").Length > 0) { return(Tuple.Create(ExoOperationResult.DropFail, failMessage)); } } } return(Tuple.Create(ExoOperationResult.Success, (string)null)); }
/// <summary> /// コンストラクタ。 /// </summary> /// <param name="processes">VOICEROIDプロセスコレクション。</param> /// <param name="canUseConfig">各設定値を利用可能な状態であるか否か。</param> /// <param name="talkTextReplaceConfig">トークテキスト置換設定値。</param> /// <param name="exoConfig">AviUtl拡張編集ファイル用設定値。</param> /// <param name="appConfig">アプリ設定値。</param> /// <param name="uiConfig">UI設定値。</param> /// <param name="lastStatus">直近のアプリ状態値の設定先。</param> /// <param name="openFileDialogService">ファイル選択ダイアログサービス。</param> /// <param name="windowActivateService">ウィンドウアクティブ化サービス。</param> /// <param name="voiceroidActionService"> /// VOICEROIDプロセスアクションサービス。 /// </param> /// <param name="aviUtlFileDropService"> /// AviUtl拡張編集ファイルドロップサービス。 /// </param> public MainWindowViewModel( IReadOnlyCollection <IProcess> processes, IReadOnlyReactiveProperty <bool> canUseConfig, IReadOnlyReactiveProperty <TalkTextReplaceConfig> talkTextReplaceConfig, IReadOnlyReactiveProperty <ExoConfig> exoConfig, IReadOnlyReactiveProperty <AppConfig> appConfig, IReadOnlyReactiveProperty <UIConfig> uiConfig, IReactiveProperty <IAppStatus> lastStatus, IOpenFileDialogService openFileDialogService, IWindowActivateService windowActivateService, IVoiceroidActionService voiceroidActionService, IAviUtlFileDropService aviUtlFileDropService) { ValidateArgumentNull(processes, nameof(processes)); ValidateArgumentNull(canUseConfig, nameof(canUseConfig)); ValidateArgumentNull(talkTextReplaceConfig, nameof(talkTextReplaceConfig)); ValidateArgumentNull(exoConfig, nameof(exoConfig)); ValidateArgumentNull(appConfig, nameof(appConfig)); ValidateArgumentNull(uiConfig, nameof(uiConfig)); ValidateArgumentNull(lastStatus, nameof(lastStatus)); ValidateArgumentNull(openFileDialogService, nameof(openFileDialogService)); ValidateArgumentNull(windowActivateService, nameof(windowActivateService)); ValidateArgumentNull(voiceroidActionService, nameof(voiceroidActionService)); ValidateArgumentNull(aviUtlFileDropService, nameof(aviUtlFileDropService)); this.IsTopmost = this.MakeInnerPropertyOf(appConfig, c => c.IsTopmost); // VoiceroidViewModel 作成 var canModifyNotifier = new ReactiveProperty <bool>(false).AddTo(this.CompositeDisposable); this.Voiceroid = new VoiceroidViewModel( processes, canUseConfig, talkTextReplaceConfig, exoConfig, appConfig, uiConfig, lastStatus, canModifyNotifier, windowActivateService, voiceroidActionService, aviUtlFileDropService) .AddTo(this.CompositeDisposable); // 設定変更可能状態 var canModify = new[] { canUseConfig, canModifyNotifier } .CombineLatestValuesAreAllTrue() .ToReadOnlyReactiveProperty() .AddTo(this.CompositeDisposable); // その他 ViewModel 作成 this.TalkTextReplaceConfig = new TalkTextReplaceConfigViewModel( canModify, talkTextReplaceConfig, appConfig, uiConfig, lastStatus) .AddTo(this.CompositeDisposable); this.ExoConfig = new ExoConfigViewModel( canModify, exoConfig, appConfig, uiConfig, lastStatus, openFileDialogService) .AddTo(this.CompositeDisposable); this.AppConfig = new AppConfigViewModel( canModify, appConfig, uiConfig, lastStatus, openFileDialogService) .AddTo(this.CompositeDisposable); this.LastStatus = new AppStatusViewModel(lastStatus).AddTo(this.CompositeDisposable); }