diff --git a/FP Auto Video Converter 2/EncoderSettings.cs b/FP Auto Video Converter 2/EncoderSettings.cs
new file mode 100644
index 0000000..549c17f
--- /dev/null
+++ b/FP Auto Video Converter 2/EncoderSettings.cs
@@ -0,0 +1,57 @@
+namespace FP_Auto_Video_Converter_2
+{
+ enum EncoderKind { Cpu, Nvenc, Qsv }
+
+ // Кожен варіант кодування налаштовується тут окремо і самодостатньо — щоб додати
+ // новий (напр. AMD AMF) пізніше, досить додати один case у кожен метод нижче плюс
+ // одну радіокнопку й одну пробу визначення заліза в Form1, більше нічого чіпати не треба.
+ static class EncoderSettings
+ {
+ public static string GetHwaccelPrefix(EncoderKind kind)
+ {
+ switch (kind)
+ {
+ case EncoderKind.Nvenc: return "-hwaccel cuda ";
+ default: return ""; // QSV: тестувався без -hwaccel qsv — воно апаратно не вміє декодувати ProRes/4:2:2, додавати без окремої перевірки ризиковано
+ }
+ }
+
+ public static string BuildVideoCodecArgs(EncoderKind kind, int crf, string cpuPreset, string nvencPreset)
+ {
+ switch (kind)
+ {
+ case EncoderKind.Nvenc:
+ // Калібровано на 4 різних типах контенту (шумне відео, чистий ProRes-майстер,
+ // анімація, динамічна сцена). Будь-який фіксований офсет -cq (+1..+4), що рятував
+ // розмір на одному контенті, провалював якість на іншому (найгірше: +4 → VMAF
+ // впало з 97 до 85 на чистому ProRes). НЕ додавати офсет без повторного калібрування.
+ return $"-c:v hevc_nvenc -preset {nvencPreset} -tune hq -rc vbr -cq {crf} -b:v 0";
+ case EncoderKind.Qsv:
+ // Калібрований лише на 2 з 4 типів контенту (там -global_quality≈CRF-4 підходив).
+ // З огляду на урок з NVENC (офсет з малої вибірки виявився шкідливим) — свідомо
+ // консервативно: CRF напряму, без офсету. Без -preset: тестувався лише з -global_quality.
+ return $"-c:v hevc_qsv -global_quality {crf}";
+ default:
+ return $"-c:v libx265 -preset {cpuPreset} -crf {crf}";
+ }
+ }
+
+ // hevc_nvenc падає (0 байт, "Failed setup for format cuda") на 4:2:2-джерелах (напр. ProRes).
+ // QSV такого бага не має (перевірено).
+ public static bool NeedsPixelFormatFix(EncoderKind kind) => kind == EncoderKind.Nvenc;
+
+ // В той самий -vf, що й scale (через кому) — ніколи окремим -pix_fmt (знищив би 10-біт HDR).
+ public const string PixelFormatFixFilter = "format=yuv420p|p010le";
+
+ // Пробне кодування 1 кадру — найпростіший універсальний спосіб перевірити, чи реально
+ // працює апаратний кодер на цій машині (а не просто "чи є відеокарта в списку"), без
+ // прав адміністратора і без нових залежностей (WMI тощо).
+ public static string GetProbeArguments(EncoderKind kind)
+ {
+ string codec = kind == EncoderKind.Nvenc ? "hevc_nvenc" : "hevc_qsv";
+ // 64x64 виявилось замалим - і NVENC, і QSV відмовляють у кодуванні нижче свого
+ // мінімального розміру ("Current resolution is unsupported"). 320x240 - безпечний мінімум.
+ return $"-hide_banner -loglevel error -f lavfi -i testsrc=duration=1:size=320x240:rate=1 -c:v {codec} -f null -";
+ }
+ }
+}
diff --git a/FP Auto Video Converter 2/FP Auto Video Converter 2.csproj b/FP Auto Video Converter 2/FP Auto Video Converter 2.csproj
index 8bc50c7..4c7cd20 100644
--- a/FP Auto Video Converter 2/FP Auto Video Converter 2.csproj
+++ b/FP Auto Video Converter 2/FP Auto Video Converter 2.csproj
@@ -7,7 +7,7 @@
{31A0748C-2E9A-4E67-B5E8-643C68D8E320}
WinExe
FP_Auto_Video_Converter_2
- FP Auto Video Converter 2.5
+ FP Auto Video Converter 2.7
v3.5
512
true
@@ -52,6 +52,7 @@
Form1.cs
+
diff --git a/FP Auto Video Converter 2/Form1.Designer.cs b/FP Auto Video Converter 2/Form1.Designer.cs
index 6ba2256..968ccb8 100644
--- a/FP Auto Video Converter 2/Form1.Designer.cs
+++ b/FP Auto Video Converter 2/Form1.Designer.cs
@@ -76,6 +76,11 @@ private void InitializeComponent()
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.labelCrfMeaning = new System.Windows.Forms.Label();
this.trackBarCRF = new System.Windows.Forms.TrackBar();
+ this.groupBoxEncoder = new System.Windows.Forms.GroupBox();
+ this.labelEncoderMeaning = new System.Windows.Forms.Label();
+ this.radioButtonCpu = new System.Windows.Forms.RadioButton();
+ this.radioButtonNvenc = new System.Windows.Forms.RadioButton();
+ this.radioButtonQsv = new System.Windows.Forms.RadioButton();
this.label1 = new System.Windows.Forms.Label();
this.buttonStop = new System.Windows.Forms.Button();
this.buttonStart = new System.Windows.Forms.Button();
@@ -93,6 +98,7 @@ private void InitializeComponent()
((System.ComponentModel.ISupportInitialize)(this.trackBarPreset)).BeginInit();
this.groupBox2.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarCRF)).BeginInit();
+ this.groupBoxEncoder.SuspendLayout();
this.panel2.SuspendLayout();
this.SuspendLayout();
//
@@ -281,18 +287,19 @@ private void InitializeComponent()
this.panel1.Controls.Add(this.checkBoxSkipIfBigger);
this.panel1.Controls.Add(this.groupBox3);
this.panel1.Controls.Add(this.groupBox2);
+ this.panel1.Controls.Add(this.groupBoxEncoder);
this.panel1.Controls.Add(this.label1);
this.panel1.Controls.Add(this.buttonStop);
this.panel1.Controls.Add(this.buttonStart);
this.panel1.Dock = System.Windows.Forms.DockStyle.Right;
this.panel1.Location = new System.Drawing.Point(1171, 0);
this.panel1.Name = "panel1";
- this.panel1.Size = new System.Drawing.Size(361, 904);
+ this.panel1.Size = new System.Drawing.Size(361, 954);
this.panel1.TabIndex = 3;
//
// textBoxReduceFramerateValue
//
- this.textBoxReduceFramerateValue.Location = new System.Drawing.Point(259, 224);
+ this.textBoxReduceFramerateValue.Location = new System.Drawing.Point(259, 357);
this.textBoxReduceFramerateValue.Name = "textBoxReduceFramerateValue";
this.textBoxReduceFramerateValue.Size = new System.Drawing.Size(53, 22);
this.textBoxReduceFramerateValue.TabIndex = 23;
@@ -308,7 +315,7 @@ private void InitializeComponent()
this.groupBox1.Controls.Add(this.buttonRemoveLessMBit);
this.groupBox1.Controls.Add(this.buttonClearSelected);
this.groupBox1.Controls.Add(this.buttonRemoveH265);
- this.groupBox1.Location = new System.Drawing.Point(16, 294);
+ this.groupBox1.Location = new System.Drawing.Point(16, 427);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(331, 127);
this.groupBox1.TabIndex = 22;
@@ -415,7 +422,7 @@ private void InitializeComponent()
this.panel3.Controls.Add(this.labelStats);
this.panel3.Controls.Add(this.buttonOpenRecycle);
this.panel3.Dock = System.Windows.Forms.DockStyle.Bottom;
- this.panel3.Location = new System.Drawing.Point(0, 623);
+ this.panel3.Location = new System.Drawing.Point(0, 673);
this.panel3.Name = "panel3";
this.panel3.Size = new System.Drawing.Size(361, 281);
this.panel3.TabIndex = 16;
@@ -500,7 +507,7 @@ private void InitializeComponent()
//
// textBoxScaleDownSmallerSide
//
- this.textBoxScaleDownSmallerSide.Location = new System.Drawing.Point(264, 253);
+ this.textBoxScaleDownSmallerSide.Location = new System.Drawing.Point(264, 386);
this.textBoxScaleDownSmallerSide.Name = "textBoxScaleDownSmallerSide";
this.textBoxScaleDownSmallerSide.Size = new System.Drawing.Size(79, 22);
this.textBoxScaleDownSmallerSide.TabIndex = 18;
@@ -511,7 +518,7 @@ private void InitializeComponent()
//
this.checkBoxScaleDown.Checked = true;
this.checkBoxScaleDown.CheckState = System.Windows.Forms.CheckState.Checked;
- this.checkBoxScaleDown.Location = new System.Drawing.Point(21, 246);
+ this.checkBoxScaleDown.Location = new System.Drawing.Point(21, 379);
this.checkBoxScaleDown.Name = "checkBoxScaleDown";
this.checkBoxScaleDown.Size = new System.Drawing.Size(267, 40);
this.checkBoxScaleDown.TabIndex = 17;
@@ -524,7 +531,7 @@ private void InitializeComponent()
//
this.checkBoxReduceFramerate.Checked = true;
this.checkBoxReduceFramerate.CheckState = System.Windows.Forms.CheckState.Checked;
- this.checkBoxReduceFramerate.Location = new System.Drawing.Point(21, 217);
+ this.checkBoxReduceFramerate.Location = new System.Drawing.Point(21, 350);
this.checkBoxReduceFramerate.Name = "checkBoxReduceFramerate";
this.checkBoxReduceFramerate.Size = new System.Drawing.Size(305, 40);
this.checkBoxReduceFramerate.TabIndex = 15;
@@ -536,7 +543,7 @@ private void InitializeComponent()
//
this.checkBoxSkipIfBigger.Checked = true;
this.checkBoxSkipIfBigger.CheckState = System.Windows.Forms.CheckState.Checked;
- this.checkBoxSkipIfBigger.Location = new System.Drawing.Point(21, 188);
+ this.checkBoxSkipIfBigger.Location = new System.Drawing.Point(21, 321);
this.checkBoxSkipIfBigger.Name = "checkBoxSkipIfBigger";
this.checkBoxSkipIfBigger.Size = new System.Drawing.Size(330, 40);
this.checkBoxSkipIfBigger.TabIndex = 14;
@@ -550,7 +557,7 @@ private void InitializeComponent()
this.groupBox3.Controls.Add(this.labelPresetMeaning);
this.groupBox3.Controls.Add(this.trackBarPreset);
this.groupBox3.Controls.Add(this.labelPresetName);
- this.groupBox3.Location = new System.Drawing.Point(16, 99);
+ this.groupBox3.Location = new System.Drawing.Point(16, 232);
this.groupBox3.Name = "groupBox3";
this.groupBox3.Size = new System.Drawing.Size(331, 80);
this.groupBox3.TabIndex = 13;
@@ -595,7 +602,7 @@ private void InitializeComponent()
//
this.groupBox2.Controls.Add(this.labelCrfMeaning);
this.groupBox2.Controls.Add(this.trackBarCRF);
- this.groupBox2.Location = new System.Drawing.Point(16, 10);
+ this.groupBox2.Location = new System.Drawing.Point(16, 143);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(331, 83);
this.groupBox2.TabIndex = 11;
@@ -624,13 +631,74 @@ private void InitializeComponent()
this.trackBarCRF.TabIndex = 10;
this.trackBarCRF.Value = 30;
this.trackBarCRF.ValueChanged += new System.EventHandler(this.trackBarCRF_ValueChanged);
- //
+ //
+ // groupBoxEncoder
+ //
+ this.groupBoxEncoder.Controls.Add(this.labelEncoderMeaning);
+ this.groupBoxEncoder.Controls.Add(this.radioButtonQsv);
+ this.groupBoxEncoder.Controls.Add(this.radioButtonNvenc);
+ this.groupBoxEncoder.Controls.Add(this.radioButtonCpu);
+ this.groupBoxEncoder.Location = new System.Drawing.Point(16, 10);
+ this.groupBoxEncoder.Name = "groupBoxEncoder";
+ this.groupBoxEncoder.Size = new System.Drawing.Size(331, 123);
+ this.groupBoxEncoder.TabIndex = 25;
+ this.groupBoxEncoder.TabStop = false;
+ this.groupBoxEncoder.Text = "Кодування";
+ this.toolTip1.SetToolTip(this.groupBoxEncoder, "GPU працює значно швидше за CPU. Показуються лише ті варіанти, які реально " +
+ "доступні на цьому комп\'ютері.");
+ //
+ // radioButtonCpu
+ //
+ this.radioButtonCpu.Checked = true;
+ this.radioButtonCpu.Location = new System.Drawing.Point(9, 19);
+ this.radioButtonCpu.Name = "radioButtonCpu";
+ this.radioButtonCpu.Size = new System.Drawing.Size(316, 24);
+ this.radioButtonCpu.TabIndex = 0;
+ this.radioButtonCpu.TabStop = true;
+ this.radioButtonCpu.Text = "CPU (libx265)";
+ this.radioButtonCpu.UseVisualStyleBackColor = true;
+ this.radioButtonCpu.CheckedChanged += new System.EventHandler(this.radioButtonEncoder_CheckedChanged);
+ //
+ // radioButtonNvenc
+ //
+ this.radioButtonNvenc.Location = new System.Drawing.Point(9, 42);
+ this.radioButtonNvenc.Name = "radioButtonNvenc";
+ this.radioButtonNvenc.Size = new System.Drawing.Size(316, 24);
+ this.radioButtonNvenc.TabIndex = 1;
+ this.radioButtonNvenc.Text = "NVIDIA GPU (NVENC)";
+ this.radioButtonNvenc.UseVisualStyleBackColor = true;
+ this.radioButtonNvenc.Visible = false;
+ this.radioButtonNvenc.CheckedChanged += new System.EventHandler(this.radioButtonEncoder_CheckedChanged);
+ //
+ // radioButtonQsv
+ //
+ this.radioButtonQsv.Location = new System.Drawing.Point(9, 65);
+ this.radioButtonQsv.Name = "radioButtonQsv";
+ this.radioButtonQsv.Size = new System.Drawing.Size(316, 24);
+ this.radioButtonQsv.TabIndex = 2;
+ this.radioButtonQsv.Text = "Intel GPU (QSV)";
+ this.radioButtonQsv.UseVisualStyleBackColor = true;
+ this.radioButtonQsv.Visible = false;
+ this.radioButtonQsv.CheckedChanged += new System.EventHandler(this.radioButtonEncoder_CheckedChanged);
+ //
+ // labelEncoderMeaning
+ //
+ this.labelEncoderMeaning.AutoSize = false;
+ this.labelEncoderMeaning.Font = new System.Drawing.Font("Microsoft Sans Serif", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
+ this.labelEncoderMeaning.ForeColor = System.Drawing.SystemColors.GrayText;
+ this.labelEncoderMeaning.Location = new System.Drawing.Point(6, 88);
+ this.labelEncoderMeaning.Name = "labelEncoderMeaning";
+ this.labelEncoderMeaning.Size = new System.Drawing.Size(319, 32);
+ this.labelEncoderMeaning.TabIndex = 3;
+ this.labelEncoderMeaning.Text = "CPU: повільніше, але без обмежень до якості/сумісності.";
+ this.labelEncoderMeaning.TextAlign = System.Drawing.ContentAlignment.TopCenter;
+ //
// label1
//
this.label1.AutoSize = true;
this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
this.label1.ForeColor = System.Drawing.SystemColors.GrayText;
- this.label1.Location = new System.Drawing.Point(18, 500);
+ this.label1.Location = new System.Drawing.Point(18, 633);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(313, 32);
this.label1.TabIndex = 9;
@@ -642,7 +710,7 @@ private void InitializeComponent()
//
this.buttonStop.Enabled = false;
this.buttonStop.Image = global::FP_Auto_Video_Converter_2.Properties.Resources.stop;
- this.buttonStop.Location = new System.Drawing.Point(275, 440);
+ this.buttonStop.Location = new System.Drawing.Point(275, 573);
this.buttonStop.Name = "buttonStop";
this.buttonStop.Size = new System.Drawing.Size(74, 57);
this.buttonStop.TabIndex = 7;
@@ -655,7 +723,7 @@ private void InitializeComponent()
//
this.buttonStart.Font = new System.Drawing.Font("Microsoft Sans Serif", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
this.buttonStart.Image = global::FP_Auto_Video_Converter_2.Properties.Resources.play;
- this.buttonStart.Location = new System.Drawing.Point(18, 440);
+ this.buttonStart.Location = new System.Drawing.Point(18, 573);
this.buttonStart.Name = "buttonStart";
this.buttonStart.Size = new System.Drawing.Size(251, 57);
this.buttonStart.TabIndex = 3;
@@ -691,15 +759,15 @@ private void InitializeComponent()
this.AllowDrop = true;
this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
- this.ClientSize = new System.Drawing.Size(1532, 904);
+ this.ClientSize = new System.Drawing.Size(1532, 954);
this.Controls.Add(this.splitContainer1);
this.Controls.Add(this.panel2);
this.Controls.Add(this.panel1);
this.Font = new System.Drawing.Font("Microsoft Sans Serif", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
- this.MinimumSize = new System.Drawing.Size(1298, 871);
+ this.MinimumSize = new System.Drawing.Size(1298, 921);
this.Name = "Form1";
- this.Text = "FP AutoVideoConverter 2.5";
+ this.Text = "FP AutoVideoConverter 2.7";
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Form1_FormClosing);
this.Load += new System.EventHandler(this.Form1_Load);
this.DragDrop += new System.Windows.Forms.DragEventHandler(this.Form1_DragDrop);
@@ -720,6 +788,7 @@ private void InitializeComponent()
this.groupBox2.ResumeLayout(false);
this.groupBox2.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.trackBarCRF)).EndInit();
+ this.groupBoxEncoder.ResumeLayout(false);
this.panel2.ResumeLayout(false);
this.ResumeLayout(false);
@@ -753,6 +822,11 @@ private void InitializeComponent()
private System.Windows.Forms.Panel panel3;
private System.Windows.Forms.CheckBox checkBoxScaleDown;
private System.Windows.Forms.TextBox textBoxScaleDownSmallerSide;
+ private System.Windows.Forms.GroupBox groupBoxEncoder;
+ private System.Windows.Forms.RadioButton radioButtonCpu;
+ private System.Windows.Forms.RadioButton radioButtonNvenc;
+ private System.Windows.Forms.RadioButton radioButtonQsv;
+ private System.Windows.Forms.Label labelEncoderMeaning;
private System.Windows.Forms.Button buttonClearBacups;
private System.Windows.Forms.ToolTip toolTip1;
private System.Windows.Forms.Button buttonRemoveLessPx;
diff --git a/FP Auto Video Converter 2/Form1.cs b/FP Auto Video Converter 2/Form1.cs
index 2613021..d7697af 100644
--- a/FP Auto Video Converter 2/Form1.cs
+++ b/FP Auto Video Converter 2/Form1.cs
@@ -37,6 +37,16 @@ public partial class Form1 : Form
Thread workingThread;
Process ffmpeg = null;
+ // Кеш результатів проби апаратних кодерів на цій машині (null = проба ще триває).
+ static readonly Dictionary encoderAvailability = new Dictionary
+ {
+ { EncoderKind.Cpu, true },
+ { EncoderKind.Nvenc, null },
+ { EncoderKind.Qsv, null },
+ };
+ Thread nvencProbeThread;
+ Thread qsvProbeThread;
+
private Stopwatch convertTime = new Stopwatch(); //convert time
private Stopwatch upTime = new Stopwatch(); //app start
@@ -169,6 +179,15 @@ private void Form1_Load(object sender, EventArgs e)
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
log("Комп’ютер не перейде в режим сну, поки працює ця програма.");
+ // Проба апаратних кодерів у фоні - не затримує старт вікна.
+ // CPU завжди доступний, тому пробуємо лише NVIDIA й Intel.
+ nvencProbeThread = new Thread(() => ProbeEncoderAsync(EncoderKind.Nvenc));
+ nvencProbeThread.IsBackground = true;
+ nvencProbeThread.Start();
+ qsvProbeThread = new Thread(() => ProbeEncoderAsync(EncoderKind.Qsv));
+ qsvProbeThread.IsBackground = true;
+ qsvProbeThread.Start();
+
processArgumentsOnLoad();
@@ -189,6 +208,10 @@ private string getArgumentsHelp()
"\n\n -scaleN - Зняти галочку \"Зменшити роздільну здатність до\"." +
"\n\n -crf33 - Задати CRF=33 (або інше число)." +
"\n\n -preset4 - Задати preset=faster (або інший, число до 10)." +
+ "\n\n -gpuY - Вибрати кодування NVIDIA GPU (NVENC), якщо доступне на цій машині." +
+ "\n\n -gpuN - Вибрати кодування CPU." +
+ "\n\n -qsvY - Вибрати кодування Intel GPU (QSV), якщо доступне на цій машині." +
+ "\n\n -qsvN - Вибрати кодування CPU." +
"\n\n -clearResolution1080 - Очистити зі списку всі файли менші за 1080 по меншій стороні (або інше число)." +
"\n\n -clearBitrate10 - Очистити зі списку всі файли бітрейтом менше 10 мегабіт (або інше число)." +
"\n\n -clearH265 - Очистити зі списку всі файли що вже в кодеку H265 (HEVC)." +
@@ -234,6 +257,14 @@ void processArgumentsOnLoad()
if (getArgument("-preset*", out int preset))
if (preset <= trackBarPreset.Maximum && preset >= trackBarPreset.Minimum)
trackBarPreset.Value = preset;
+ if (isArgument("-gpuY"))
+ SelectEncoderIfAvailable(EncoderKind.Nvenc);
+ if (isArgument("-gpuN"))
+ radioButtonCpu.Checked = true;
+ if (isArgument("-qsvY"))
+ SelectEncoderIfAvailable(EncoderKind.Qsv);
+ if (isArgument("-qsvN"))
+ radioButtonCpu.Checked = true;
// Перевірка, чи є адреса папки в аргументах
@@ -777,6 +808,9 @@ public void buttonsActive(bool active)
checkBoxSkipIfBigger.Enabled = active;
textBoxScaleDownSmallerSide.Enabled = active;
checkBoxScaleDown.Enabled = active;
+ radioButtonCpu.Enabled = active;
+ radioButtonNvenc.Enabled = active;
+ radioButtonQsv.Enabled = active;
buttonExit.Enabled = active;
if (active)
{
@@ -874,6 +908,8 @@ private void buttonStart_Click(object sender, EventArgs e)
log("Збір даних...");
int crf = trackBarCRF.Value;
getPresetInfo(trackBarPreset.Value, out string preset, out string description);
+ EncoderKind encoderKind = GetSelectedEncoderKind();
+ string gpuPreset = getGpuPreset(trackBarPreset.Value);
bool reduceFramerate = checkBoxReduceFramerate.Checked;
int.TryParse(textBoxReduceFramerateValue.Text, out int reduceFramerateValue);
if (reduceFramerateValue < 1 || reduceFramerateValue > 240)
@@ -891,6 +927,7 @@ private void buttonStart_Click(object sender, EventArgs e)
}
log($"CRF = {crf}");
+ log($"encoderKind = {encoderKind}");
if(reduceFramerate)
log($"reduceFramerateValue = {reduceFramerateValue}");
log($"skipBigger = {skipBigger}");
@@ -901,7 +938,7 @@ private void buttonStart_Click(object sender, EventArgs e)
stop = false;
buttonStop.Enabled = true;
convertTime.Start();
- workingThread = new Thread(() => runConvertAsync(crf, preset, reduceFramerate, reduceFramerateValue, skipBigger, downscale, downscaleSmallerSide));
+ workingThread = new Thread(() => runConvertAsync(crf, preset, encoderKind, gpuPreset, reduceFramerate, reduceFramerateValue, skipBigger, downscale, downscaleSmallerSide));
workingThread.Start();
}
catch (Exception ex)
@@ -910,7 +947,7 @@ private void buttonStart_Click(object sender, EventArgs e)
}
}
- private void runConvertAsync(int crf, string preset, bool reduceFramerate, int reduceFramerateValue, bool skipBigger, bool downscale, double targetSmallerSide)
+ private void runConvertAsync(int crf, string preset, EncoderKind encoderKind, string gpuPreset, bool reduceFramerate, int reduceFramerateValue, bool skipBigger, bool downscale, double targetSmallerSide)
{
try
{
@@ -944,7 +981,7 @@ private void runConvertAsync(int crf, string preset, bool reduceFramerate, int r
//Стиснути відео в тимчасовий файл
double videoDuration = GetVideoDurationInSeconds(filePath);
- string resolution = "";
+ List vfFilters = new List();
if (downscale && smallerSide > targetSmallerSide)
{
double scaleFactor = targetSmallerSide / smallerSide; //less 1
@@ -960,10 +997,16 @@ private void runConvertAsync(int crf, string preset, bool reduceFramerate, int r
newWidth = newHeight;
newHeight = temp;
}
- resolution = $"-vf \"scale={newWidth}:{newHeight}\" ";
+ vfFilters.Add($"scale={newWidth}:{newHeight}");
}
+ if (EncoderSettings.NeedsPixelFormatFix(encoderKind))
+ vfFilters.Add(EncoderSettings.PixelFormatFixFilter);
+ string resolution = vfFilters.Count > 0 ? $"-vf \"{string.Join(",", vfFilters.ToArray())}\" " : "";
+
string framerate = reduceFramerate? $"-r {reduceFramerateValue} " : "";
- string arguments = $"-i \"{filePath}\" -c:v libx265 -preset {preset} -crf {crf} {framerate}{resolution}-progress pipe:1 \"{tmpfile}\"";
+ string hwaccel = EncoderSettings.GetHwaccelPrefix(encoderKind);
+ string videoCodec = EncoderSettings.BuildVideoCodecArgs(encoderKind, crf, preset, gpuPreset);
+ string arguments = $"{hwaccel}-i \"{filePath}\" {videoCodec} {framerate}{resolution}-progress pipe:1 \"{tmpfile}\"";
string exe = "ffmpeg.exe";
log(exe + " " + arguments);
ffmpeg = new Process();
@@ -980,10 +1023,16 @@ private void runConvertAsync(int crf, string preset, bool reduceFramerate, int r
{
log(args.Data);
string timeStr = args.Data.Split('=')[1].Split(' ')[0]; //Парсимо поточний Час у форматі hh:mm:ss.xx
- TimeSpan currentTime = TimeSpan.Parse(timeStr);
- double percentage = (currentTime.TotalSeconds / videoDuration) * 100;
- status($"Прогрес файлу: {percentage:F2}%");
- updateStats();
+ // На перших кадрах (буфер/lookahead кодека) ffmpeg іноді видає від'ємний
+ // time= (напр. "-00:00:00.03") - TimeSpan.Parse на такому падає з
+ // необробленим винятком у фоновому потоці, що аварійно завершує програму.
+ // TryParse замість Parse - пропускаємо один кадр прогресу, не падаємо.
+ if (TimeSpan.TryParse(timeStr, out TimeSpan currentTime))
+ {
+ double percentage = (currentTime.TotalSeconds / videoDuration) * 100;
+ status($"Прогрес файлу: {percentage:F2}%");
+ updateStats();
+ }
}
}
};
@@ -1259,6 +1308,121 @@ private void trackBarPreset_ValueChanged(object sender, EventArgs e)
labelPresetMeaning.Text = description;
}
+ EncoderKind GetSelectedEncoderKind()
+ {
+ if (radioButtonNvenc.Checked) return EncoderKind.Nvenc;
+ if (radioButtonQsv.Checked) return EncoderKind.Qsv;
+ return EncoderKind.Cpu;
+ }
+
+ private void radioButtonEncoder_CheckedChanged(object sender, EventArgs e)
+ {
+ switch (GetSelectedEncoderKind())
+ {
+ case EncoderKind.Nvenc:
+ labelEncoderMeaning.Text = "NVIDIA GPU (hevc_nvenc): значно швидше, потребує підтримки NVENC.";
+ break;
+ case EncoderKind.Qsv:
+ labelEncoderMeaning.Text = "Intel GPU (hevc_qsv): значно швидше, потребує підтримки Quick Sync.";
+ break;
+ default:
+ labelEncoderMeaning.Text = "CPU (libx265): повільніше, але без обмежень до якості/сумісності.";
+ break;
+ }
+ }
+
+ // Пробує реально закодувати 1 кадр через апаратний кодер і перевіряє, чи запрацювало.
+ // Запускається у фоновому потоці з Form1_Load, щоб не затримувати старт вікна.
+ void ProbeEncoderAsync(EncoderKind kind)
+ {
+ bool available = false;
+ string debugInfo = "";
+ try
+ {
+ string baseDir = AppDomain.CurrentDomain.BaseDirectory;
+ Process probe = new Process();
+ probe.StartInfo.FileName = Path.Combine(baseDir, "ffmpeg.exe");
+ probe.StartInfo.Arguments = EncoderSettings.GetProbeArguments(kind);
+ probe.StartInfo.WorkingDirectory = baseDir;
+ probe.StartInfo.UseShellExecute = false;
+ probe.StartInfo.CreateNoWindow = true;
+ probe.StartInfo.RedirectStandardOutput = true;
+ probe.StartInfo.RedirectStandardError = true;
+ probe.Start();
+ string stderr = probe.StandardError.ReadToEnd();
+ bool finished = probe.WaitForExit(4000);
+ if (!finished)
+ {
+ try { probe.Kill(); } catch { }
+ available = false;
+ debugInfo = "timeout";
+ }
+ else
+ {
+ available = probe.ExitCode == 0;
+ debugInfo = $"exitCode={probe.ExitCode} stderr={stderr}";
+ }
+ }
+ catch (Exception ex)
+ {
+ available = false;
+ debugInfo = "exception: " + ex.Message;
+ }
+ // Спершу записуємо результат, і лише потім чіпаємо UI. Це важливо: коли пробу
+ // очікують через Join() (SelectEncoderIfAvailable, CLI-автоматизація), UI-потік
+ // заблокований — якби ми чіпали UI (через Invoke) РАНІШЕ за цей запис, вийшов би
+ // deadlock (UI чекає на потік проби, потік проби чекає на Invoke до UI).
+ encoderAvailability[kind] = available;
+ log($"{kind}: {(available ? "доступний" : "недоступний")}" + (debugInfo.Length > 0 && !available ? $" ({debugInfo})" : ""));
+ if (available)
+ RevealEncoderOption(kind);
+ }
+
+ // Використовується лише з CLI-прапорців (-gpuY/-qsvY) для автоматизації через BAT.
+ // На відміну від звичайного інтерактивного старту, тут свідомо чекаємо (обмежено) на
+ // пробу заліза - інакше швидкий автоматизований запуск міг би тихо впасти на CPU,
+ // навіть якщо потрібне апаратне кодування насправді доступне.
+ void SelectEncoderIfAvailable(EncoderKind kind)
+ {
+ Thread probeThread = kind == EncoderKind.Nvenc ? nvencProbeThread : qsvProbeThread;
+ probeThread?.Join(4500);
+
+ if (encoderAvailability.TryGetValue(kind, out bool? avail) && avail == true)
+ {
+ RadioButton rb = kind == EncoderKind.Nvenc ? radioButtonNvenc : radioButtonQsv;
+ rb.Visible = true;
+ rb.Checked = true;
+ }
+ else
+ {
+ log($"Аргумент запросив {kind}, але цей енкодер недоступний на цій машині. Залишено CPU.");
+ }
+ }
+
+ void RevealEncoderOption(EncoderKind kind)
+ {
+ RadioButton rb = kind == EncoderKind.Nvenc ? radioButtonNvenc : radioButtonQsv;
+ // BeginInvoke (не Invoke) навмисно: якщо UI-потік у цю мить чекає в Join()
+ // (SelectEncoderIfAvailable), синхронний Invoke тут заблокував би обидва потоки
+ // назавжди. BeginInvoke лише ставить виклик у чергу й не чекає на відповідь.
+ if (rb.InvokeRequired)
+ {
+ rb.BeginInvoke(new MethodInvoker(() => RevealEncoderOption(kind)));
+ return;
+ }
+ rb.Visible = true;
+ }
+
+ // NVENC-пресети (p1..p7) не збігаються з x264/x265-пресетами.
+ // Мапимо той самий повзунок 0..9 пропорційно на p1 (найшвидший) .. p7 (найповільніший).
+ string getGpuPreset(int sliderValue)
+ {
+ int max = trackBarPreset.Maximum;
+ int nvencLevel = 1 + (int)Math.Round(sliderValue * 6.0 / max);
+ nvencLevel = Math.Max(1, Math.Min(7, nvencLevel));
+ return "p" + nvencLevel;
+ }
+
private void buttonClearBacups_Click(object sender, EventArgs e)
{
DialogResult result = MessageBox.Show(
@@ -1414,7 +1578,7 @@ private void buttonExit_Click(object sender, EventArgs e)
private void buttonAbout_Click(object sender, EventArgs e)
{
- string description = "FP AutoVideoConverter 2.5" +
+ string description = "FP AutoVideoConverter 2.7" +
"\n" +
"\nЦя програма дозволяє автоматизувати процес стиснення відеофайлів у кодек H.265 (HEVC), " +
"надаючи зручний інтерфейс для пакетного стиснення великої кількості відео." +
@@ -1557,4 +1721,20 @@ private void buttonHelpTerminal_Click(object sender, EventArgs e)
- Заповнено інформацію про файл
- Допрацьована обробка помилок - програма не вилітає якщо помилка обробки файлу
- Допрацьовано отримання формату файлу - не виникає помилки якщо в відео файлі присутні кілька потоків відео
+
+2.6
+- Додано підтримку апаратного кодування NVIDIA NVENC (GPU) як альтернативу CPU (libx265)
+- В інтерфейсі додано перемикач "Використовувати GPU (NVIDIA NVENC)"
+- Додано аргументи командного рядка -gpuY / -gpuN
+- Оновлено ffmpeg.exe до версії 8.1.2 (gyan.dev full build) - стара збірка 2016 року не мала NVENC/CUDA
+
+2.7
+- Додано підтримку Intel Quick Sync Video (QSV) як третій варіант кодування поруч з CPU/NVIDIA
+- Перемикач замінено з чекбоксу на 3 радіокнопки (CPU/NVIDIA GPU/Intel GPU), перенесено нагору панелі налаштувань
+- Додано автовизначення заліза при старті - показуються лише ті варіанти, що реально працюють на цій машині (без потреби в правах адміністратора; перевірка пробним кодуванням, а не через WMI)
+- Логіка кожного кодера винесена в окремий файл EncoderSettings.cs - додати новий варіант (напр. AMD AMF) у майбутньому можна без змін в іншому коді
+- Виправлено баг: hevc_nvenc падав (0 байт) на 4:2:2-джерелах (напр. ProRes) - тепер формат пікселів нормалізується перед кодуванням, з збереженням 10-біт HDR де він є
+- Виправлено deadlock, через який -gpuY міг тихо відкочуватись на CPU при автоматизованому запуску
+- Виправлено крах програми при від'ємному time= у виводі ffmpeg (буває на перших кадрах при кодуванні з B-кадрами)
+- Додано аргументи командного рядка -qsvY / -qsvN
*/
\ No newline at end of file
diff --git a/FP Auto Video Converter 2/Properties/AssemblyInfo.cs b/FP Auto Video Converter 2/Properties/AssemblyInfo.cs
index 2c1365f..3093b51 100644
--- a/FP Auto Video Converter 2/Properties/AssemblyInfo.cs
+++ b/FP Auto Video Converter 2/Properties/AssemblyInfo.cs
@@ -6,7 +6,7 @@
// Общие сведения об этой сборке предоставляются следующим набором
// набора атрибутов. Измените значения этих атрибутов для изменения сведений,
// связанных со сборкой.
-[assembly: AssemblyTitle("FP Auto Video Converter 2.5")]
+[assembly: AssemblyTitle("FP Auto Video Converter 2.7")]
[assembly: AssemblyDescription("Auto Video Converter")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Dr. Failov")]
@@ -33,6 +33,6 @@
// Можно задать все значения или принять номера сборки и редакции по умолчанию
// используя "*", как показано ниже:
// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("2.5.0.0")]
-[assembly: AssemblyFileVersion("2.5.0.0")]
+[assembly: AssemblyVersion("2.7.0.0")]
+[assembly: AssemblyFileVersion("2.7.0.0")]
[assembly: NeutralResourcesLanguage("uk")]
diff --git a/README.md b/README.md
index 780d125..9975fb1 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
-# 🎬 FP Auto Video Converter 2.5
+# 🎬 FP Auto Video Converter 2.7
@@ -22,7 +22,8 @@
✅ **Можливість запуску з командного рядка** – для автоматизації процесу.
✅ **Підтримка різних відеоформатів** – програма працює з багатьма популярними форматами.
✅ **Пакетна обробка файлів** – можна додавати **цілі папки** для обробки.
-✅ **Гнучкі налаштування** – дозволяє задавати **параметри стиснення**.
+✅ **Гнучкі налаштування** – дозволяє задавати **параметри стиснення**.
+✅ **Апаратне прискорення** – окрім CPU підтримує **NVIDIA (NVENC)** та **Intel Quick Sync (QSV)**; програма сама визначає, що доступно на вашому ПК.
---
@@ -90,6 +91,8 @@
- `-scaleN` — Зняти галочку **"Зменшити роздільну здатність до"**.
- `-crf33` — Задати **CRF=33** (або інше число).
- `-preset4` — Задати **preset=faster** (або інший, число до 10).
+- `-gpuY` / `-gpuN` — Обрати кодування через **NVIDIA GPU (NVENC)** / повернутись на CPU.
+- `-qsvY` / `-qsvN` — Обрати кодування через **Intel GPU (QSV)** / повернутись на CPU.
## 🗑 Очищення списку файлів
- `-clearResolution1080` — Видалити файли, менші за **1080** по меншій стороні (або інше число).
@@ -110,4 +113,4 @@
---
**Розробник:** Dr. Failov
-📅 2025
+📅 2025-2026
diff --git a/Releases/FP Auto Video Converter 2.6 Release 20260715.zip b/Releases/FP Auto Video Converter 2.6 Release 20260715.zip
new file mode 100644
index 0000000..ec694d2
Binary files /dev/null and b/Releases/FP Auto Video Converter 2.6 Release 20260715.zip differ
diff --git a/Releases/FP Auto Video Converter 2.7 Release 20260716.zip b/Releases/FP Auto Video Converter 2.7 Release 20260716.zip
new file mode 100644
index 0000000..b680318
Binary files /dev/null and b/Releases/FP Auto Video Converter 2.7 Release 20260716.zip differ