/// <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));
        }
示例#2
0
        /// <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;
        }
示例#3
0
        /// <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);
        }
示例#4
0
        /// <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);
        }