Skip to content

Commit e1933f3

Browse files
seto77claude
andcommitted
Migrate video export from GPL ffmpeg to Windows Media Foundation
Rotation-animation video export previously bundled a GPL ffmpeg build (libx264/libx265) loaded through FFMediaToolkit. Replace it with the OS-provided Windows Media Foundation H.264/H.265 encoder. - Add MediaFoundationVideoEncoder (Crystallography.Controls) using direct mfplat.dll / mfreadwrite.dll P/Invoke; no third-party dependency. - Rewrite FormMovie to feed BGRA32 frames to the new encoder, disable H.265 when no HEVC encoder is available, and tighten bitmap/Form disposal. - Remove the 11 bundled ffmpeg DLLs, the FFMediaToolkit package reference, and the installer's ffmpeg folder. - Update THIRD-PARTY-NOTICES.md: no ffmpeg/x264/x265/GPL binaries are bundled, so the former GPL source-availability obligation no longer applies. - Version history: ver4.934. - gitignore per-run GUI capture logs (docs/src/assets/**/_capture-log.tsv). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7b46d83 commit e1933f3

18 files changed

Lines changed: 467 additions & 110 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ docs/hooks/__pycache__/
66

77
# 260526Cl: Ignore the local Python venv used to run MkDocs.
88
docs/.venv-docs/
9+
10+
# 260530Cl: Ignore per-run GUI capture logs (byproduct of --capture; PNGs are tracked, logs are not).
11+
docs/src/assets/**/_capture-log.tsv

Crystallography.Controls/MediaFoundationVideoEncoder.cs

Lines changed: 381 additions & 0 deletions
Large diffs are not rendered by default.

ReciPro/FormMovie.cs

Lines changed: 66 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
using Crystallography.OpenGL;
2-
using FFMediaToolkit; //260405Cl 追加
3-
using FFMediaToolkit.Encoding; //260405Cl 追加
4-
using FFMediaToolkit.Graphics; //260405Cl 追加
2+
//260530Cl Media Foundation へ移行: FFMediaToolkit は不使用 (旧 using をコメントアウト)。MediaFoundationVideoEncoder は Crystallography.Controls (Program.cs の global using 経由)
3+
//using FFMediaToolkit; //260405Cl 追加
4+
//using FFMediaToolkit.Encoding; //260405Cl 追加
5+
//using FFMediaToolkit.Graphics; //260405Cl 追加
56
using System.Drawing;
67
using System.Drawing.Imaging;
78
using System.Collections.Generic; //260405Cl 追加
8-
using System.IO;
9-
using System.Reflection;
109
using System.Threading.Tasks; //260405Cl 追加
1110
using System.Windows.Forms;
1211

@@ -30,7 +29,7 @@ public partial class FormMovie : FormBase
3029

3130
public Matrix3D Rot => FormMain.Crystal.RotationMatrix;
3231

33-
private static bool ffmpegLoaded = false; //260405Cl 追加
32+
//260530Cl Media Foundation 移行で不要: private static bool ffmpegLoaded = false;
3433
private bool encoding = false; //260405Cl 追加: エンコード中フラグ
3534

3635
public bool MillerBravaisIndex { set => indexControl.MillerBravais = value; }
@@ -40,6 +39,12 @@ public FormMovie()
4039
{
4140
InitializeComponent();
4241
comboBoxSpeed.SelectedIndex = 7;
42+
// 260530Cl このマシンで H.265(HEVC) エンコーダが使えない場合は H.265 を選べないようにする
43+
if (!MediaFoundationVideoEncoder.IsHevcEncoderAvailable())
44+
{
45+
radioButtonH265.Enabled = false;
46+
radioButtonH264.Checked = true;
47+
}
4348
}
4449

4550
private void buttonDirection_Click(object sender, EventArgs e)
@@ -102,20 +107,18 @@ private async void buttonOK_Click(object sender, EventArgs e)
102107
{
103108
FormMain.Enabled = Caller.Enabled = false;
104109

105-
//FFmpegライブラリの初期化 (初回のみ) 260405Cl
106-
if (!ffmpegLoaded)
107-
{
108-
var ffmpegDir = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "ffmpeg");
109-
foreach (var name in new[] { "libwinpthread-1", "libgcc_s_seh-1", "libstdc++-6", "zlib1",
110-
"libx264-165", "libx265", "avutil-59", "swresample-5", "avcodec-61",
111-
"avformat-61", "swscale-8" })
112-
System.Runtime.InteropServices.NativeLibrary.Load(Path.Combine(ffmpegDir, name + ".dll"));
113-
FFmpegLoader.FFmpegPath = ffmpegDir;
114-
ffmpegLoaded = true;
115-
}
110+
// 260530Cl ffmpeg(GPL) の動的ロードを廃止。Media Foundation は OS 内蔵のため初期化不要。
111+
// 旧コード:
112+
// if (!ffmpegLoaded) {
113+
// var ffmpegDir = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "ffmpeg");
114+
// foreach (var name in new[] { "libwinpthread-1", "libgcc_s_seh-1", "libstdc++-6", "zlib1",
115+
// "libx264-165", "libx265", "avutil-59", "swresample-5", "avcodec-61", "avformat-61", "swscale-8" })
116+
// NativeLibrary.Load(Path.Combine(ffmpegDir, name + ".dll"));
117+
// FFmpegLoader.FFmpegPath = ffmpegDir; ffmpegLoaded = true;
118+
// }
116119

117120
var framerate = 30;
118-
var codec = radioButtonH264.Checked ? VideoCodec.H264 : VideoCodec.H265; //260405Cl 変更
121+
var hevc = radioButtonH265.Checked; // 260530Cl VideoCodec→bool に変更 (H.265=true, H.264=false)
119122
var speed = (string)comboBoxSpeed.SelectedItem;
120123

121124
//UIスレッドで全フレームのビットマップデータを収集 260405Cl
@@ -126,34 +129,29 @@ private async void buttonOK_Click(object sender, EventArgs e)
126129
FormMain.Rotate(Direction, Speed * Math.PI / framerate / 180.0);
127130
var bmp = Target is GLControlAlpha c ? c.GenerateBitmap() : Func();
128131
if (bmp.Width % 2 != 0 || bmp.Height % 2 != 0)
129-
bmp = bmp.Clone(new Rectangle(0, 0, bmp.Width - bmp.Width % 2, bmp.Height - bmp.Height % 2), bmp.PixelFormat);
132+
{
133+
var cropped = bmp.Clone(new Rectangle(0, 0, bmp.Width - bmp.Width % 2, bmp.Height - bmp.Height % 2), bmp.PixelFormat);
134+
bmp.Dispose(); // 260530Cl Clone 元のビットマップを解放 (リーク防止)
135+
bmp = cropped;
136+
}
130137
width = bmp.Width;
131138
height = bmp.Height;
132-
//Bitmapのピクセルデータをbyte[]にコピー
139+
//260530Cl Bitmapを上から下・隙間なしの BGRA(32bpp) として byte[] に収集 (Media Foundation 入力用)
140+
// 旧: Format24bppRgb で stride 付きデータを収集していた
133141
var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
134-
var bits = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
135-
var stride = bits.Stride;
136-
var data = new byte[stride * bmp.Height];
142+
var bits = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
143+
var data = new byte[bmp.Width * 4 * bmp.Height]; // 32bpp は stride==width*4 でパディングなし
137144
System.Runtime.InteropServices.Marshal.Copy(bits.Scan0, data, 0, data.Length);
138145
bmp.UnlockBits(bits);
146+
bmp.Dispose(); // 260530Cl 収集後にビットマップを解放 (毎フレームのリーク防止)
139147
frames.Add(data);
140148
}
141149

142150
//バックグラウンドスレッドでエンコード 260405Cl
143151
var fileName = dlg.FileName;
144-
var preset = speed switch
145-
{
146-
"ultrafast" => EncoderPreset.UltraFast,
147-
"superfast" => EncoderPreset.SuperFast,
148-
"veryfast" => EncoderPreset.VeryFast,
149-
"faster" => EncoderPreset.Faster,
150-
"fast" => EncoderPreset.Fast,
151-
"medium" => EncoderPreset.Medium,
152-
"slow" => EncoderPreset.Slow,
153-
"slower" => EncoderPreset.Slower,
154-
"veryslow" => EncoderPreset.VerySlow,
155-
_ => EncoderPreset.Medium,
156-
};
152+
// 260530Cl x264 由来の preset(ultrafast..veryslow) は Media Foundation では未使用
153+
// (レート制御は MediaFoundationVideoEncoder 側で Quality モード固定)。combo は進捗表示ラベル用に残す。
154+
// 旧: var preset = speed switch { "ultrafast" => EncoderPreset.UltraFast, ... _ => EncoderPreset.Medium };
157155

158156
encoding = true; //260405Cl
159157

@@ -167,37 +165,44 @@ private async void buttonOK_Click(object sender, EventArgs e)
167165
StartPosition = FormStartPosition.CenterParent,
168166
MinimizeBox = false, MaximizeBox = false, ControlBox = false,
169167
};
170-
var codecName = codec == VideoCodec.H264 ? "H.264" : "H.265"; //260405Cl
168+
var codecName = hevc ? "H.265" : "H.264"; //260530Cl
171169
var progressBar = new ProgressBar { Dock = DockStyle.Fill, Maximum = frames.Count, Style = ProgressBarStyle.Continuous };
172170
progressForm.Text = $"{codecName} / {speed} - 0.0% - 00:00:00 / --:--:--";
173171
progressForm.Controls.Add(progressBar);
174-
progressForm.Show(this);
175172

176-
var sw = System.Diagnostics.Stopwatch.StartNew(); //260405Cl
177-
var progress = new Progress<int>(v =>
178-
{
179-
progressBar.Value = v;
180-
var percent = 100.0 * v / frames.Count;
181-
var elapsed = sw.Elapsed;
182-
var remaining = v > 0 ? TimeSpan.FromTicks(elapsed.Ticks * (frames.Count - v) / v) : TimeSpan.Zero;
183-
progressForm.Text = $"{codecName} / {speed} - {percent:0.0}% - {elapsed:hh\\:mm\\:ss} / {remaining:hh\\:mm\\:ss}";
184-
});
185-
await Task.Run(() =>
173+
try // 260530Cl 例外時もフォーム再有効化・progressForm 破棄・encoding 解除を保証する
186174
{
187-
var settings = new VideoEncoderSettings(width, height, framerate, codec) { EncoderPreset = preset };
188-
using var file = MediaBuilder.CreateContainer(fileName).WithVideo(settings).Create();
189-
var size = new System.Drawing.Size(width, height);
190-
for (int i = 0; i < frames.Count; i++)
191-
{
192-
file.Video.AddFrame(ImageData.FromArray(frames[i], ImagePixelFormat.Bgr24, size));
193-
((IProgress<int>)progress).Report(i + 1);
194-
}
195-
});
175+
progressForm.Show(this);
196176

197-
progressForm.Close();
198-
encoding = false; //260405Cl
199-
200-
FormMain.Enabled = Caller.Enabled = true;
177+
var sw = System.Diagnostics.Stopwatch.StartNew(); //260405Cl
178+
var progress = new Progress<int>(v =>
179+
{
180+
if (progressForm.IsDisposed) return; // 260530Cl Dispose 後の遅延 Report を無視 (ObjectDisposedException 防止)
181+
progressBar.Value = v;
182+
var percent = 100.0 * v / frames.Count;
183+
var elapsed = sw.Elapsed;
184+
var remaining = v > 0 ? TimeSpan.FromTicks(elapsed.Ticks * (frames.Count - v) / v) : TimeSpan.Zero;
185+
progressForm.Text = $"{codecName} / {speed} - {percent:0.0}% - {elapsed:hh\\:mm\\:ss} / {remaining:hh\\:mm\\:ss}";
186+
});
187+
await Task.Run(() =>
188+
{
189+
//260530Cl Media Foundation(OS 内蔵)で H.264/H.265 → MP4 を出力 (旧: FFMediaToolkit/ffmpeg)
190+
using var encoder = new MediaFoundationVideoEncoder(fileName, width, height, framerate, hevc);
191+
for (int i = 0; i < frames.Count; i++)
192+
{
193+
encoder.AddFrameBgra32(frames[i]);
194+
frames[i] = null; // 260530Cl エンコード済みフレームを解放しピークメモリを抑える
195+
((IProgress<int>)progress).Report(i + 1);
196+
}
197+
encoder.Finish();
198+
});
199+
}
200+
finally
201+
{
202+
progressForm.Dispose(); // 260530Cl 非モーダル Form は Close では破棄されないため Dispose
203+
encoding = false; //260405Cl
204+
FormMain.Enabled = Caller.Enabled = true;
205+
}
201206
}
202207
}
203208

ReciPro/ReciPro.csproj

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,7 @@
102102
<Content Include="doc\ReciProManual(ja).pdf">
103103
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
104104
</Content>
105-
<!-- 260405Cl ffmpeg.exe → FFMediaToolkit + 最小DLLに変更 -->
106-
<Content Include="ffmpeg\*.dll">
107-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
108-
</Content>
105+
<!-- 260530Cl GPL の ffmpeg DLL 同梱を廃止し Media Foundation へ移行。旧: <Content Include="ffmpeg\*.dll"> -->
109106
<Content Include="initial.xml">
110107
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
111108
</Content>
@@ -126,7 +123,7 @@
126123

127124
<ItemGroup>
128125
<PackageReference Include="BitMiracle.LibTiff.NET" Version="2.4.660" />
129-
<PackageReference Include="FFMediaToolkit" Version="4.8.1" />
126+
<!-- 260530Cl FFMediaToolkit 廃止 (Media Foundation へ移行) -->
130127
<PackageReference Include="IronPython" Version="3.4.2" />
131128
<PackageReference Include="MathNet.Numerics" Version="6.0.0-beta2" />
132129
<!--260405Cl MathNet.Numerics.MKL.Win-x64 を削除 (オプション化。ネイティブDLLは実行時にダウンロード)-->

ReciPro/Version.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ internal static class Version
77

88
public const string History =
99
"History" +
10+
"\r\n ver4.934(2026/05/30) Improved the video encoding engine and reduced the distribution size." +
1011
"\r\n ver4.933(2026/05/29) Fixed OpenGL rendering corruption on Windows on ARM (x64 emulation)." +
1112
"\r\n ver4.932(2026/05/28) Improved the manual and enhanced stereographic projection features. (see https://github.com/seto77/ReciPro/issues/58)" +
1213
"\r\n ver4.931(2026/05/19) Fixed GUI layout issues that occurred under high-DPI display settings. (see https://github.com/seto77/ReciPro/issues/59)" +

ReciPro/ffmpeg/avcodec-61.dll

-474 KB
Binary file not shown.

ReciPro/ffmpeg/avformat-61.dll

-523 KB
Binary file not shown.

ReciPro/ffmpeg/avutil-59.dll

-999 KB
Binary file not shown.

ReciPro/ffmpeg/libgcc_s_seh-1.dll

-148 KB
Binary file not shown.

ReciPro/ffmpeg/libstdc++-6.dll

-2.35 MB
Binary file not shown.

0 commit comments

Comments
 (0)