Open23

YMM4 Plugin 制作メモ

ピン留めされたアイテム
いぬいぬいぬいぬ

ゆっくりムービーメーカー4(YMM4)のプラグイン機能について

有志

自作

解説記事

https://zenn.dev/inuinu/articles/how2build-ymm4-synthesis-plugin

いぬいぬいぬいぬ

VSCode + dotnet cliで開発する

公式には本家VSの開発情報だけだが、csprojの書き方工夫でVSCode+dotnet cliでもプラグイン作れる。

csprojの書き方

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net7.0-windows10.0.19041.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

  <ItemGroup>
    <!-- YMM4 plugins -->
    <Reference Include="path\to\YukkuriMovieMaker4\YukkuriMovieMaker.Plugin.dll" />
    <Reference Include="path\to\YukkuriMovieMaker4\YukkuriMovieMaker.Controls.dll" />
    <!-- 環境変数を使う場合
    <Reference Include="$(YMM4_PATH)\YukkuriMovieMaker.Plugin.dll" />
    <Reference Include="$(YMM4_PATH)\YukkuriMovieMaker.Controls.dll" />
    -->
    <!-- ビルド時にdllを展開しない -->
    <!-- 
    <Reference Include="$(YMM4_PATH)\YukkuriMovieMaker.Plugin.dll">
      <Private>false</Private>
      <CopyLocal>false</CopyLocal>
    </Reference>
    <Reference Include="$(YMM4_PATH)\YukkuriMovieMaker.Controls.dll">
      <Private>false</Private>
      <CopyLocal>false</CopyLocal>
    </Reference>
      -->
  </ItemGroup>
</Project>

cliコマンドdotnet add referenceでいけるかどうかは不明なので手書き。

パス

YMM4のメニューの「ヘルプ」>「その他」>「アプリケーションフォルダを開く」でYukkuriMovieMaker.Plugin.dllへのパスを取得。

環境変数などに登録しておくと良い

  • 例:YMM4_PATH
  • csproj内では $(YMM4_PATH)でパスが参照できる
csprj
    <Reference Include="$(YMM4_PATH)\YukkuriMovieMaker.Plugin.dll" />
    <Reference Include="$(YMM4_PATH)\YukkuriMovieMaker.Controls.dll" />

SampleSAPI5VoicePluginはdotnet cliでビルドできない

manju-summoner/YukkuriMovieMaker4PluginSamples: YMM4用プラグインのサンプル集です

以下にあるように、利用しているmsbuildが.NET Framework版である必要がある。そのままのdotnet buildでは.NET Core系のmsbuildが呼ばれるため。

https://qiita.com/up-hash/items/c3b813a24cd8d0247855

https://learn.microsoft.com/ja-jp/visualstudio/msbuild/resolvecomreference-task?view=vs-2022

いぬいぬいぬいぬ

.NET SDK 8.0 移行

YMM v4.23.0.0で.NET7から.NET8に移行したため、プロジェクトの<TargetFramework>をnet7.0-windows10.0.19041.0からnet8.0-windows10.0.19041.0に変更する必要があります。

ゆっくりMovieMaker v4.22.x.x以前に作成されたプロジェクトファイルの移行手順

未検証だけど <TargetFrameworks>で.NET7/8両対応ビルドできるかも?

<TargetFrameworks>net7.0-windows10.0.19041.0;net8.0-windows10.0.19041.0;</TargetFrameworks>

dllの依存関係でダメかな?
参照先を変えれば?

→ だめだった…

いぬいぬいぬいぬ

dotnet publish で ymme をつくる

やりかた

  • MSBuildをつかってpublish -> zip圧縮 -> ymmeリネーム -> プラグインフォルダへ配置まで一括でやる

csproj

  • $(YMM4_PATH)は環境変数で通しておく

  • ビルド前に出力先をクリアしておくTarget

csproj ビルド前に出力先をクリアしておく(古いバイナリ残らない様に)
 <Target Name="RemovePublishDirBeforeBuild" BeforeTargets="BeforeBuild">
    <RemoveDir Directories="$(OutputPath)/publish/" />
    <RemoveDir Directories="$(YMM4_PATH)\user\plugin\$(AssemblyName)\"/>
    <Message Text="RemovePublishDirBeforeBuild" Importance="high" />
  </Target>
  • publish後にいろいろするTarget
publish後の処理
  <Target Name="MakeZipPackage" AfterTargets="Publish">
    <!-- 一旦ymmeの出力先 -->
    <MakeDir Directories="$(OutputPath)/../../../../../publish/" />

    <ItemGroup>
      <!-- 不要なファイルをymmeに取り込まない様にここで指定する -->
      <FilesToDelete Include="$(OutputPath)\publish\YukkuriMovieMaker.*.dll"/>
    </ItemGroup>

    <Delete Files="@(FilesToDelete)">
       <Output
          TaskParameter="DeletedFiles"
          ItemName="FilesDeleted"/>
     </Delete>

    <!-- zip圧縮、ymme拡張子変更 -->
    <ZipDirectory
      SourceDirectory="$(OutputPath)/publish/"
      DestinationFile="$(OutputPath)/../../../../../publish/$(AssemblyName).ymme"
      Overwrite="true" />
    <Message Text="Actions After Publish" Importance="high" />

    <!-- ※ここから下はやらなくてもymmeは既にできてる。そのままテストしたい場合用 -->
    <!-- プラグインフォルダ以下にコピー -->
    <Copy
      SourceFiles="$(OutputPath)/../../../../../publish/$(AssemblyName).ymme"
      DestinationFolder="$(YMM4_PATH)\user\plugin\"
      />
    <!-- ymmeを展開して使えるようにする -->
    <Unzip
      SourceFiles="$(OutputPath)/../../../../../publish/$(AssemblyName).ymme" DestinationFolder="$(YMM4_PATH)\user\plugin\$(AssemblyName)\" />
  </Target>
いぬいぬいぬいぬ

素材一覧に製作者・ニコニコIDを表示する

プラグイン本体

PluginDetails属性
[PluginDetails(AuthorName = "YourName", ContentId = "YourNicoNicoID")]
public MyPlugin: IPlugin {}

または

IPlugin.Details を実装する
public PluginDetailsAttribute Details => new()
{
    //制作者
    AuthorName = "InuInu",
    //作品ID
    ContentId = "",
};

※PluginDetails属性だけだと反映されないバグ? or プラグインの種類がある模様

その場合、面倒なのでリフレクションで取得。

public PluginDetailsAttribute Details
		=> GetType().GetCustomAttribute<PluginDetailsAttribute>()
			?? new();

ボイス(声質)

IVoiceSpeaker
//ボイス(声質)の制作者
public string? SpeakerAuthor { get; } = "test";
//ボイス(声質)の作品ID (ニコニコIDとして有効じゃないとダメ)
public string? SpeakerContentId { get; } = "nc9999999";
//合成エンジン(アプリケーション)の制作者
public string? EngineAuthor { get; } = "test3";
//合成エンジン(アプリケーション)の作品ID (ニコニコIDとして有効じゃないとダメ)
public string? EngineContentId { get; } = "nc9999999";

VideoEffect、AudioEffect

PluginDetails属性

いぬいぬいぬいぬ

デバッグ(YMM4 + VSCode)

以下の設定でデバッガー接続できる。

launch.json

  • 環境変数 YMM_PATH は以下のように ${env:YMM4_PATH}で参照可能。
  • "justMyCode": falseが無いとたぶんダメ。
{
    "name": "Debug YMM4",
    "type": "coreclr",
    "request": "launch",
    "preLaunchTask": "build",
    "program": "${env:YMM4_PATH}/YukkuriMovieMaker.exe",
    "args": [],
    "cwd": "${workspaceFolder}",
    "stopAtEntry": false,
    "console": "internalConsole",
    "justMyCode": false,
    "logging": {
        "moduleLoad": false
    }
}

csproj

  • dotnet build時にdllがYMM4のプラグインフォルダ以下にコピーすればデバッグできる
    • pdbも必要
    • ライブラリが使っているdllなどすべてがbin/Debug以下にあるわけではないのでpublishしたときのものを追加でコピーする
      • 例:bin/Release/publish 以下のdllを全部持ってくる
      • 注:publishした時pdbを消す処理していると除外されるのでどうにかする
csproj
<Target Name="CopyDebugDlls" BeforeTargets="AfterBuild" Condition="'$(Configuration)' == 'Debug'">
    <ItemGroup>
      <CommonPaths Include="$(OutputPath)../../Release/$(TargetFramework)/publish"/>

      <MissingDlls Include="$(CommonPaths)/*.dll" Exclude="$(CommonPaths)/$(AssemblyName).dll"/>
      <MissingPdbs Include="$(CommonPaths)/*.pdb" Exclude="$(CommonPaths)/$(AssemblyName).pdb"/>
    </ItemGroup>
    <Copy SourceFiles="@(MissingDlls)" DestinationFolder="$(OutputPath)" SkipUnchangedFiles="true"/>
    <Copy SourceFiles="@(MissingPdbs)" DestinationFolder="$(OutputPath)" SkipUnchangedFiles="true"/>

    <ItemGroup>
      <SourceFiles Include="$(OutputPath)**/*.*" />
    </ItemGroup>
    <Copy SourceFiles="@(SourceFiles)" DestinationFolder="$(YMM4_PATH)\user\plugin\$(AssemblyName)\" />
  </Target>

参考

いぬいぬいぬいぬ

プラグインの動的なUI付きパラメータ

  • パラメータをコレクションで持たせてカスタムコントロールを作るケース
  • パラメータをコレクションで持たせてカスタム属性で表示するケース
    • 入れ子パラメータ内で既存のTextBoxSliderとか使いたい場合
      がある(後者は饅頭遣いさんから教えていただいた)

コレクションパラメータ

[Display(AutoGenerateField = true)]で入れ子内の表示を自動生成に任せる

MyParentParameter.cs
[Display(AutoGenerateField = true)]
public ImmutableList<MyChildParameter> ItemsCollection {/*...*/}

カスタム属性

  • CustomDisplayAttributeBaseを継承した属性を作成。
    • リフレクションでごにょごにょして表示させる
  • カスタム表示させたいプロパティに付与
MyChildParameter.cs
[MyCustomDisplay]
[TextBoxSlider("F2", "", 0, 1, Delay = -1)]
[Range(0, 1)]
[DefaultValue(0.0)]
public double Value { get => _value; set => Set(ref _value, value);}
いぬいぬいぬいぬ

Windowsタスクバーに進捗表示

var main = Application.Current.MainWindow;
var taskbar = new TaskbarItemInfo
{
    ProgressState = TaskbarItemProgressState.Indeterminate,
};
main.TaskbarItemInfo = taskbar;

//do something...

taskbar.ProgressState = TaskbarItemProgressState.None;
  • Application.Current.MainWindow でYMM4のメインウィンドウを取得

  • TaskbarItemInfoに割り当てる

  • ProgressStateで進捗表示。ProgressValueで比率も指定できる。

  • 注:UIスレッドの処理になるのでワーカースレッドから呼ぶ場合は注意が必要

    • EpoxyのUIThreadクラスなどを使うと楽
//EpoxyのUIThreadクラスを使う(この外はワーカースレッドなので呼べない)
await UIThread.InvokeAsync(()=>{
    //ここはUIスレッドになってるのでここで呼び出す
    TaskbarUtil.StartIndeterminate();
    return ValueTask.CompletedTask;
}).ConfigureAwait(false);

いぬいぬいぬいぬ

YMM4プラグイン用githubのテンプレートリポジトリ

https://github.com/InuInu2022/ymme-vscode-boilerplate

  • src/Sample以下に何もしないサンプルプラグインが設定済みです
    • これをもとにしても、公式のサンプルを元にしてもOK
    • ソリューションファイル:Sample.slnも設定済みです
  • dotnet publishコマンドでymmeファイルをpublish以下に作成&YMM4のプラグインフォルダ以下に展開できます
  • Code Analyzerを色々有効にしています
  • ビルド時に LICENSEREADME.md がdllやymmeに含まれるように設定済
  • licensesフォルダ以下のファイルもdllやymmeに含まれるように
  • MinVerライブラリでgitのタグから自動でSemVerが付きます
  • VSCode: VSCodeのpublishタスクでも上記ができるようにしています
  • VSCode: プラグインをYMM4ごとデバッガ接続で起動することができます
  • gitignore, editorconfig設定済み

https://docs.github.com/ja/repositories/creating-and-managing-repositories/creating-a-template-repository

いぬいぬいぬいぬ

FocusHelper

  • YukkuriMovieMaker.Commons.FocusHelper
    • 外部のアプリ操作のときなどにYMM4にフォーカスを戻すことができる
//現在のフォーカスされたUI部品(WPFの`FrameworkElement`)を取得できる
//v4.45.1で削除
FocusHelper.DefaultFocus;

//YMM4のウィンドウ内の要素にフォーカスを移す
FocusHelper.FocusWindowContent(DependencyObject dependencyObject);

//外部アプリなどにフォーカスが移ったあとにYMM4にフォーカスを戻す
Window.GetWindow(FocusHelper.DefaultFocus).Activate();
FocusHelper.FocusWindowContent(FocusHelper.DefaultFocus);
いぬいぬいぬいぬ

IToolPlugin

IToolPluginを実装したプラグインを作り、View(UserControl)とViewModelの型を渡すと、YMM4のツールメニューから呼び出し可能なツールが作れる。

public class MyToolPlugin : IToolPlugin
{
	public string Name => "MyToolPlugin";

	public Type ViewModelType
		=> typeof(MyToolViewModel);
	public Type ViewType
		=> typeof(MyToolView);
}

ただし、YMM4内部のAPIとかは他の形式のプラグイン以下の事しかできないため、
ある程度独自処理のツールしか作れない。

サンプル

これ↓が参考になるかも。

https://github.com/InuInu2022/SampleYmmeGetProjInfo/tree/main/src/Sample

作り方

必須ソースファイル

最低限、次の5つのソースファイルがあれば作れます。
例:SampleToolPluginという名前のプラグインを作る時

  • SampleToolPlugin.cs
    • IToolPluginを実装したSampleToolPluginクラスを定義
  • SampleToolPlugin.csproj
    • プラグインのプロジェクトの設定ファイル
    • 他の種類のプラグインと同じ
  • SampleToolView.xaml
    • ツールのGUIを定義するXAML
    • WindowではなくUserControl
  • SampleToolView.xaml.cs
    • SampleToolViewのコードビハインドクラス
  • SampleToolViewModel.cs
    • ViewModelクラス
    • ツールプラグインはMVVMパターンでの実装が想定されています

あるといいソースファイル

必須ではないですが、IToolPlugin向けのAPIはほとんど何もないので、以下のソースを使うとできることの幅が広がります。

  • SampleToolPluginSettings.cs
    • SettingsBase<T>を継承した設定保存処理クラス
    • 他のプラグインと同じくプラグインの設定を保存することができます
    • Categoryプロパティは現状SettingsCategory.Noneを指定しておくといいかと思います
    • ツールプラグインだと使うことが多いかも
  • SampleToolPluginSettingsView.xaml
    • 設定のGUIのXAML(UserControl
    • SampleToolPluginSettings.HasSettingView = trueにすると設定のGUIを定義できます
    • GUIはYMM4本来のメニューのファイル>設定の左のリストに表示されます
      • ※ちょっと遠いので見つけにくいかも
  • SampleToolPluginSettingsView.xaml.cs
    • SampleToolPluginSettingsViewのコードビハインドクラス
    • ここに処理をかく。ViewModelを別途定義してそちらで処理しても良い。
いぬいぬいぬいぬ

ymmeファイルの命名規則

ymmeからのインストール時、zip内の親フォルダの名前をプラグインフォルダの名前として使用するように仕様変更を行いました(親フォルダが存在しない場合や、すでにzipファイルの名前でプラグインがインストールされている場合はzipファイルの名前でフォルダが作成されます)

上記対応によってymmeファイル名にバージョン番号を入れて配布できるようになった。

いぬいぬいぬいぬ

思考実験:こんなプラグインを作るとしたら…

3Dモデル表示・立ち絵プラグイン

  • Vorticeで3D描画し、出力先をDX2Dにすれば"原理的には"できる
  • 問題
    • 2D変換を書けるので多分重い
    • DX直接叩いてるのと同じなので作るのが大変(基本機能から作る必要あり)
    • 画像プラグインにするか、図形プラグインにするか
      • 画角とかのパラメータを考えると画像プラグインだとできない…?要検証
  • 使えそう

Live2D 立ち絵プラグイン

  • Live2Dのライブラリはある
  • 「SDKリリースライセンス契約(正式名称:出版許諾契約)の締結と契約料のお支払いが必要となる場合があります」に該当する?
    • プラグインじゃなくてYMM4側にかかる場合だと大事で難しい

VSTプラグイン(VST ホストプラグイン)

  • 結論
    • ライセンス取得が必要
  • 技術的には…
    • VSTホストになるYMM4音声加工プラグインを作ることができれば原理的にはできる
  • 問題がいっぱいある
    • 無料で作ろうとするとGPLになっちゃう(YMM4自体もソースコード公開義務がでちゃう)
      • Steinburgにライセンス申請&取得必要
    • C#でVSTホストを簡単に作れるライブラリがない
      • 「VST.NET」はVST2形式なので作れるけど公開できない(今公開OKなのはVST3形式)
いぬいぬいぬいぬ

YMM4本体のウィンドウを判定する

IMainViewModelっていうインターフェイスが YukkuriMovieMaker.Plugin に公開されているのでこれを使って判定できる。

WindowDataContextIMainViewModelを実装していれば、YMM4のメインウィンドウだと判定できる。またIMainViewModel.Indexプロパティでウィンドウの区別ができる模様。

List<dynamic> windows = [.. Application.Current.Windows];
var wins = windows.OfType<Window>();

foreach(var window in wins)
{
    if(window.DataContext is IMainViewModel ymmWindow)
    {
      // ymmWindowはYMM4本体のウインドウ(複数ある)
      if(ymmWindow.Index == 0){
        //最初のYMM4のメインウインドウが取れる?
      }
    }
}
いぬいぬいぬいぬ

複数のプラグインで同じライブラリを使っている場合、最初にYMM4に読まれたDLLしか使われない問題

複数のプラグインから異なるバージョンのライブラリlを参照すると、
最初にYmm4本体に読み込まれたDllしか使用されず、正しく機能しない問題があります。
(.NETの仕様)

YMM4本体で使っているライブラリの場合

YMM4本体同梱のdllを参照する様にすることでバージョン不一致問題がなく、プラグインと同時にライブラリのDLLを配布しなくとも良くなります。
プラグインの容量削減にもなります!

csproj
<Reference Include="$(YMM4_PATH)\【ライブラリのDLL名】.dll">
  <Private>false</Private>
  <CopyLocal>false</CopyLocal>
</Reference>

MSBuildのスクリプト組めば、YMM4本体で使ってるDllを動的に判定して除外することもできる。

csproj等
<!-- 不要なDLLファイルを除外(YMM4本体のDLLなど) -->
<ItemGroup>
  <!-- YMM4_PATHにあるDLLファイルを動的に取得 -->
  <Ymm4SystemDlls Include="$(YMM4_PATH)\*.dll" />

  <!-- YMM4本体のDLLを除外対象に追加 -->
  <FilesToCopy Remove="@(Ymm4SystemDlls->'$(OutputPath)%(Filename)%(Extension)')" />
</ItemGroup>

<!-- デバッグ用:除外されるDLLの一覧表示 -->
<Message Text="=== YMM4 System DLLs to exclude ===" Importance="high" />
<Message Text="%(Ymm4SystemDlls.Filename)%(Ymm4SystemDlls.Extension)" Importance="high" />

<!-- 選択されたファイルをすべてコピー -->
<Copy SourceFiles="@(FilesToCopy)"
      DestinationFiles="@(FilesToCopy->'$(PackageFolder)\%(Filename)%(Extension)')" />

YMM4本体で使ってない場合

ILRepack を利用してライブラリのDllをプラグインDLLに埋め込むことで回避できる可能性があります。

ILRepackはnugetパッケージが沢山あるんで間違えないように注意。

試行錯誤したILRepack.target
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="ILRepacker" AfterTargets="Build;AfterRebuild" BeforeTargets="PostBuildEvent"
    Condition="'$(ILRepackExecuted)' != 'true' And Exists('$(OutputPath)\YmmeUtil.Bridge.dll')" >

    <!-- 実行フラグを設定 -->
    <PropertyGroup>
      <ILRepackExecuted>true</ILRepackExecuted>
    </PropertyGroup>

    <!-- 元のファイルをバックアップ -->
    <Copy SourceFiles="$(OutputPath)\$(AssemblyName).dll" DestinationFiles="$(OutputPath)\$(AssemblyName).dll.orig" />

    <ItemGroup>
      <!-- プライマリアセンブリを最初に配置 -->
      <InputAssemblies Include="$(OutputPath)\$(AssemblyName).dll" />
      <InputAssemblies Include="$(OutputPath)\YmmeUtil.Bridge.dll" />
      <InputAssemblies Include="$(OutputPath)\YmmeUtil.Ymm4.dll" />
    </ItemGroup>

    <ItemGroup>
      <DoNotInternalizeAssemblies Include="System" />
      <DoNotInternalizeAssemblies Include="YukkuriMovieMaker" />
      <DoNotInternalizeAssemblies Include="YukkuriMovieMaker.Plugin" />
      <DoNotInternalizeAssemblies Include="YukkuriMovieMaker.Controls" />
    </ItemGroup>

    <ItemGroup>
      <LibraryPath Include="$(YMM4_PATH)" />
      <LibraryPath Include="$(OutputPath)" />
    </ItemGroup>

    <!-- 一時的な出力ファイルを使用 -->
    <ILRepack
      Parallel="true"
      Internalize="false"
      InternalizeExclude="@(DoNotInternalizeAssemblies)"
      InputAssemblies="@(InputAssemblies)"
      LibraryPath="@(LibraryPath)"
      TargetKind="Dll"
      OutputFile="$(OutputPath)\$(AssemblyName).dll"
      Union="true"
      XmlDocumentation="false"
      LogFile="$(OutputPath)\ILRepack.log"
      DebugInfo="true"
      AllowDuplicateResources="true"
      />

    <!-- マージされたDLLファイルを削除 -->
    <Delete Files="$(OutputPath)\YmmeUtil.Bridge.dll" ContinueOnError="true" />
    <Delete Files="$(OutputPath)\YmmeUtil.Ymm4.dll" ContinueOnError="true" />

    <!-- マージが成功したかを確認 -->
    <Message Text="ILRepack completed. Merged assemblies removed." Importance="high" />

  </Target>

  <!-- 重複実行を防ぐためのプロパティ設定 -->
  <PropertyGroup>
    <ILRepackExecuted Condition="'$(ILRepackExecuted)' == ''">false</ILRepackExecuted>
  </PropertyGroup>
</Project>

この場合外部のライブラリDLLを参照しないようにする必要があります。

[assembly: TypeForwardedTo(typeof(YmmeUtil.Ymm4.WindowUtil))]
いぬいぬいぬいぬ

YMM4本体のリソースを使う&同じものを使う

翻訳テキスト

YMM4の本体の翻訳データはプラグインからも利用できる。
YukkuriMoveMaker.Plugin.dllにあるYukkuriMovieMaker.Resources.Localization.Textsが利用できる。

C#コードから
using YukkuriMovieMaker.Resources.Localization;
//動画アイテムの名称。日本語だと"動画"
var localizedVideoItemName= Texts.VideoItemName;
xamlでのバインド
<UserConstrol
 xmlns:loc="clr-namespace:YukkuriMovieMaker.Resources.Localization;assembly=YukkuriMovieMaker.Plugin"
 >

<Label Content="{x:Static loc:Texts.VideoItemName}" />

</UserControl>

アイコン

icon アイコン種類 キー Material.Iconsのキー
ボイスアイテム message-text-outline MessageTextOutline
テキストアイテム `` FormatText
動画アイテム `` Video
音声アイテム `` Music
画像アイテム `` Image
図形アイテム `` ShapePlus
立ち絵アイテム `` Account
表情アイテム `` EmoticonOutline
エフェクトアイテム `` ImageAutoAdjust
場面切り替えアイテム `` GradientHorizontal
シーンアイテム `` ChartTimeline
画面の複製アイテム `` ImageMultiple
グループ制御アイテム `` SelectGroup
上のアイテムでクリッピングを有効化 vector-difference-ab+vector-intersection ``
プロジェクトを新規作成 file-plusの旧形状?
Exo出力 file-outline+filmstrip ``
動画出力 file-outline+video ``
サムネイル画像をクリップボードに出力 clipboard-outline+camera ``

アイコン色

  • YMM4Colors.IconBrushKey
  • YMM4Colors.IconBorderBrushKey

Material.Icons.WPF

https://github.com/SKProCH/Material.Icons/?tab=readme-ov-file#wpf

  • WPF版はちょっと使いにくい。
  • Size指定するか、ラップした親要素にWidth/Height指定しないと滅茶苦茶サイズが大きくなる。
  • SizeはDepandancyPropertyじゃないのでBindできない。
<!-- これだと滅茶苦茶デカクなる -->
<materialIcons:MaterialIcon Kind="Abacus" />
<Button Content="{materialIcons:MaterialIconExt Kind=Abacus}" />

<!-- 大きさはSizeのみOK. Width/Heightは指定できない。Style関係全部効かない。 -->
<materialIcons:MaterialIcon Kind="Abacus" Size="16" />
<Button Content="{materialIcons:MaterialIconExt Kind=Abacus Size="16}" />

<!-- Panel系でラップする。Style指定できるし、Bindもできる。 -->
<Border Width="{Binding IconWidth}" Height="16">
    <materialIcons:MaterialIconExt Kind="MessageTextOutline" />
</Border>

Control

Sampleリポジトリに説明のあるPropertyEditorの中ではなくて、
ツールとかオリジナルControlでのXAMLでの使い方。

YukkuriMovieMaker.Controlsを参照しておく
xmlns:c="clr-namespace:YukkuriMovieMaker.Controls;assembly=YukkuriMovieMaker.Controls"

FrameNumberEditor

フレーム表示だけは以下で機能するけど、秒数・時間表示は機能しない。

<c:FrameNumberEditor
      Name="SampleFrameNumberEditor"
      Width="200"
      DefaultValue="0"
      Max="{Binding SceneLength}"
      Min="0"
      Value="{Binding RangeStartFrame, Mode=TwoWay}"
      />
SetEditorInfo()の設定が必要
IEditorInfo info = // なんとかIEditorInfoをゲットしてくる
SampleFrameNumberEditor.SetEditorInfo(info);

IEditorInfo にはFPSとかの情報があるのでフレーム数から秒数計算のために必要。

EnumComboBox

EnumTypeに{x:Type XXXEnum}Enum型渡すと使える。

<c:EnumComboBox
    DockPanel.Dock="Right"
    EnumType="{x:Type viewmodels:LengthViewMode}"
    Value="{Binding Source={x:Static core:ObjectListSettings.Default}, Path=ShowLengthViewMode}"
    Margin="5"
  />

PropertyEditor

  • PropertyEditor自体も呼び出せる
  • 公式サンプルのSamplePolygonShapeプラグインの独自UserControl(PointsEditor)で使われてる

https://github.com/manju-summoner/YukkuriMovieMaker4PluginSamples/blob/a7c66d565629f550196185f970b7682b049739c4/YMM4SamplePlugin/Shape/SamplePolygonShape/PointsEditor.xaml#L43-L48

TextEditor

BeginEdit/EndEdit
<c:TextEditor Text="{Binding XXX}" BeginEdit="YYY" EndEdit="ZZZ"/>

YYY、ZZZの部分で親コントロールのBeginEdit、EndEditを呼び出す必要

いぬいぬいぬいぬ

PropertyEditorの属性/パラメータ

https://github.com/manju-summoner/YukkuriMovieMaker4PluginSamples/tree/a7c66d565629f550196185f970b7682b049739c4/YMM4SamplePlugin/PropertyEditor

.NET用

YMM4用属性

[FrameNumberEditor]

  • フレーム数表現につかう
  • 対象プロパティはint
  • SetEditorInfo()を呼び出さないと正しく機能しない

[RichTextEditor]

  • テキストアイテムのテキストや字幕アイテムのセリフの入力欄でつかってるUI

[CodeEditor]

  • YMM4はAvalonEditが内蔵されてるのでコードエディタのコントロールが呼び出せる

YMM4 PropertyEditorAttribute2 共通

PropertyEditorSizeパラメータ

UIの幅を指定する。

[TextEditor(PropertyEditorSize = PropertyEditorSize.Normal)]
  • Normal: 通常
  • FullWidth: 2列表示の時でもサイドバー(アイテムエディタ)の幅いっぱいに広がる
  • Half: 通常の半分

デフォルトで幅がNormal以外のものもある。

  • ToggleSliderHalf, RichTextEditorFullWidth
いぬいぬいぬいぬ

カスタムエディタのVMから*ParameterクラスのAnimationプロパティの変化を取得する

  • カスタムエディタを持つプラグインの場合
  • *ParameterクラスのAnimationプロパティの変化を通知したい

とりあえずできた方法(もっといいのがあるかも)

*ParameterをVMで取得

カスタムエディタのAttribute(例:MyCustomEditorAttribute)の SetBindingの中で、
VM(例:MyCustomEditorViewModel)の引数にItemProperty[] propertiesを渡す。

MyCustomEditorAttribute.cs
public override void SetBindings(
    FrameworkElement control,
    ItemProperty[] itemProperties
)
{
    if (control is not MyCustomEditorViewModel editor)
    {
        return;
    }

    editor.DataContext = new MyCustomEditorViewModel(
        itemProperties
    );
}

VM側で受け取ったItemProperty[] propertiesItemProperty.PropertyOwner*Parameter(例:MyCustomParameter)なのでキャストして取得する。
※配列でわたってくるのは、複数同時編集用

MyCustomEditorViewModel.cs
public MyCustomEditorViewModel(
    ItemProperty[] properties
)
{
    if(properties[0].PropertyOwner is MyCustomParameter param ){ return; }

    _param = param;
}

*ParameterクラスのAnimation型プロパティの変更通知を登録

Animation型プロパティ自体のPropertyChangedにイベントを登録しても、値の変化を検知できない。

NG
//これはダメ、なぜならAnimation型プロパティそのものの差し替えしか検知できない
_param.MyAnimProp.PropertyChanged += (s, e) => {/*...*/};
OK
//値の変化はAnimation.Valuesのコレクションそれぞれに対してイベントを登録
//Animation.Valuesの要素はINotifyPropertyChangedを実装する
foreach (var v in _param.MyAnimProp.Values.OfType<INotifyPropertyChanged>())
{
    v.PropertyChanged += (s, e) => {/*...*/};
}

Animationプロパティの値を取るためにIEditorInfoを持ってくる

やっと変更が通知取れるようになるが、今度はAnimation型のプロパティが取れない。
なぜならAnimation型プロパティは現在のフレーム、アイテムの長さフレーム数、シーンFPSの情報がないと値をGETできないから。

カスタムエディターのUserControlIPropertyEditorControl2を実装するようにすると、
SetEditorInfo()経由でIEditorInfoが取れる。

MyCustomEditor.cs
//コードビハインドクラス
public partial MyCustomEditor: UserControl, IIPropertyEditorControl2
{
    //IEditorInfoの更新あるたび毎回自動でよばれる
    public void SetEditorInfo(IEditorInfo info)
    {
        if (info is not null && DataContext is MyCustomEditorViewModel vm)
        {
            //更新受け取ったら、VMにわたす
            vm.EditorInfo = info;
        }
    }
}

IEditorInfoには ItemPositon.Frame / ItemDuration.Frame / VideoInfo.FPS があるので、
これを使ってAnimationプロパティの値をVM内で取得する。

いぬいぬいぬいぬ

プラグインで使える便利クラス

YukkuriMovieMaker.Plugin.dllにパスを通すと使える=プラグインから標準で使える

YukkuriMovieMaker.Commons.AppVersion

  • AppVersion
    • Current
      • 現在のYMM4本体のバージョンをVersion型でゲットできる

YukkuriMovieMaker.Commons.AppDirectories

YMM4関係のディレクトリパスを取れる便利クラス

  • AppDirectories
    • IsPortable
    • AppPath
    • AppDirectory
    • UserDirectory
    • PluginDirectory
    • BackupDirectory
    • LogDirectory
    • SettingDirectory
    • TemporaryDirectory
    • ResourceDirectory
    • Desktop
    • Documents
    • ComputeSettingsDirectoryHash()
      • SettingDirectoryをハッシュ値で返す
      • ユーザー名とかの個人情報を公開や記録したくない時に使う

YukkuriMovieMaker.Commons.Log

Log.Default.Write(string, Exception?)で通常のYMM4のログ・ファイルに追記できる。

  • Log.GetAnonymousExceptionString(Exception)
    • 個人情報を取り除いた例外メッセージをGETできる
いぬいぬいぬいぬ

Animation型の値を取る

*Parameterでよくあるスライダー付きの数値で、値のアニメーションを設定できるパラメータは、
Animation型というのが使われます。

これの値の取り方は少しクセがあります。
というのもアニメーション情報なので、どのタイミングかを指定しないと値が確定しないから。

GetValue(frame, length, fps)

MyAnimationPropというプロパティの場合

public Animation MyAnimationProp {/*...*/.}

double val = MyAnimationProp.GetValue(frame, length, fps);

現在のフレーム、アイテムの長さフレーム数、シーンFPSの情報がないと取得できません。
GetValue(0,0,30 /* or 60 */)で無理やり初期値を取ることもできなくはないです。

frame, length, fps

図形など

Update(timelineItemSourceDescription)の引数のTimelineItemSourceDescriptionにまとめられています。

var frame = timelineItemSourceDescription
    .ItemPosition
    .Frame;
var length = timelineItemSourceDescription
    .ItemDuration
    .Frame;
var fps = timelineItemSourceDescription.FPS;

IEditorInfo

Update()関数外や無いタイプのプラグインの場合は、IEditorInfo型のものをGETすると求められる。

たとえばIIPropertyEditorControl2を実装するカスタムエディタの場合はSetEditorInfo()が呼ばれるのでそこで取得できる。

いぬいぬいぬいぬ

v4.45+のドッキングウィンドウ関連

AvalonDock

今回のアプデのドッキングウィンドウ対応は、
WPF定番のライブラリ、AvalonDock が使われてる。

https://github.com/Dirkster99/AvalonDock

AvalonDockの基本的な構造 とか参考になります。

英語だけどgithub wikiに詳しいマニュアルがある
https://github.com/Dirkster99/AvalonDock/wiki

ツールプラグイン

IToolPluginのウィンドウはAvalonDockのおそらくLayoutAnchorableになったので、
ツールペインとしてドッキングできるようになった。

最初の起動時は子ウィンドウだけど、自分でドッキングすると次回からはドッキングされた状態に記録される。
※たぶんレイアウトの保存にも対応してるはず

プラグインからドッキング可能なWindowを作る(調査中)

  • そのままだとWPFのWindowなのでドッキングできない

  • AvalonDockのウィンドウを生成しないといけない

    • 2種類ある(ドキュメントウィンドウはLayoutDocument,ツールウィンドウはLayoutAnchorableとして定義)
  • YMM4のAvalonDockを参照しないとうまくいかない気がするのでなんとかそれをしないといけない

    • MainViewModel.LayoutService.DockingManagerにあるけど非公開
  • AvalonDock.DockingManager.CreateFloatingWindow()

    • DockingManager経由でウィンドウをつくるのはこれっぽい
  • ツリーの子に追加する方法:開発Tips① : 動的なウィンドウの追加