first commit
This commit is contained in:
commit
419bbcff26
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
bin
|
||||
obj
|
||||
input
|
||||
output
|
||||
|
||||
10
Encoder.csproj
Normal file
10
Encoder.csproj
Normal file
@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
24
Encoder.sln
Normal file
24
Encoder.sln
Normal file
@ -0,0 +1,24 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.5.2.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Encoder", "Encoder.csproj", "{39BA8241-C077-61B7-0115-1F5EA996EC8E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{39BA8241-C077-61B7-0115-1F5EA996EC8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{39BA8241-C077-61B7-0115-1F5EA996EC8E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{39BA8241-C077-61B7-0115-1F5EA996EC8E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{39BA8241-C077-61B7-0115-1F5EA996EC8E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {2E16575A-2202-4007-8A5E-CC8C9361B113}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
60
Lib/DetectWorker.cs
Normal file
60
Lib/DetectWorker.cs
Normal file
@ -0,0 +1,60 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Encoder.Lib;
|
||||
|
||||
public class DetectWorker
|
||||
{
|
||||
private readonly ProcessStartInfo startInfo;
|
||||
public string? MaxVolume { get; private set; }
|
||||
public string? MeanVolume { get; private set; }
|
||||
|
||||
public DetectWorker(string workingDirectory, string inputFile, string encodeParam)
|
||||
{
|
||||
var arguments = $"-y -i \"{inputFile}\" {encodeParam} -f null -";
|
||||
this.startInfo = new ProcessStartInfo()
|
||||
{
|
||||
FileName = "ffmpeg",
|
||||
WorkingDirectory = workingDirectory,
|
||||
Arguments = arguments,
|
||||
};
|
||||
this.MeanVolume = null;
|
||||
this.MaxVolume = null;
|
||||
}
|
||||
|
||||
public async ValueTask<int> Run()
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"{this.startInfo.FileName} {this.startInfo.Arguments}");
|
||||
using Process process = new Process();
|
||||
process.StartInfo = this.startInfo;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
|
||||
var result = process.Start();
|
||||
|
||||
if (!result)
|
||||
{
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
var str = await process.StandardError.ReadToEndAsync().ConfigureAwait(false);
|
||||
await process.WaitForExitAsync().ConfigureAwait(false);
|
||||
|
||||
// Console.WriteLine(str);
|
||||
var maxVolumeLine = str.Split('\n').FirstOrDefault(line => line.Contains("max_volume"));
|
||||
this.MaxVolume = maxVolumeLine?.Split(':').Skip(1).FirstOrDefault()?.Trim().Replace(" ", "");
|
||||
var meanVolumeLine = str.Split('\n').FirstOrDefault(line => line.Contains("mean_volume"));
|
||||
this.MeanVolume = meanVolumeLine?.Split(':').Skip(1).FirstOrDefault()?.Trim().Replace(" ", "");
|
||||
|
||||
return process.ExitCode;
|
||||
}
|
||||
catch (Exception err)
|
||||
{
|
||||
Console.WriteLine(err);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Lib/EncodeWorker.cs
Normal file
44
Lib/EncodeWorker.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Encoder.Lib;
|
||||
|
||||
public class EncodeWorker
|
||||
{
|
||||
private readonly ProcessStartInfo startInfo;
|
||||
|
||||
public EncodeWorker(string workingDirectory, string inputFile, string outputFile, string encodeParam)
|
||||
{
|
||||
var arguments = $"-y -i \"{inputFile}\" {encodeParam} \"{outputFile}\"";
|
||||
this.startInfo = new ProcessStartInfo()
|
||||
{
|
||||
FileName = "ffmpeg",
|
||||
WorkingDirectory = workingDirectory,
|
||||
Arguments = arguments,
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
CreateNoWindow = true,
|
||||
};
|
||||
}
|
||||
|
||||
public async ValueTask<int> Run()
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"{this.startInfo.FileName} {this.startInfo.Arguments}");
|
||||
using Process? process = Process.Start(this.startInfo);
|
||||
if (process is null)
|
||||
{
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
await process.StandardError.ReadToEndAsync().ConfigureAwait(false);
|
||||
await process.WaitForExitAsync().ConfigureAwait(false);
|
||||
return process.ExitCode;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
130
LimitedConcurrencyLevelTaskScheduler.cs
Normal file
130
LimitedConcurrencyLevelTaskScheduler.cs
Normal file
@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Encoder
|
||||
{
|
||||
public class LimitedConcurrencyLevelTaskScheduler
|
||||
: TaskScheduler
|
||||
{
|
||||
|
||||
public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism)
|
||||
{
|
||||
if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException(nameof(maxDegreeOfParallelism), "maxDegreeOfParallelism must be greater than zero.");
|
||||
_maxDegreeOfParallelism = maxDegreeOfParallelism;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public override int MaximumConcurrencyLevel => _maxDegreeOfParallelism;
|
||||
|
||||
private readonly int _maxDegreeOfParallelism;
|
||||
private int _delegatesQueuedOrRunning;
|
||||
|
||||
|
||||
private readonly LinkedList<Task> _tasks = new();
|
||||
private readonly object _tasksLock = new();
|
||||
|
||||
protected override IEnumerable<Task>? GetScheduledTasks()
|
||||
{
|
||||
//API仕様通り、ロックが取れたらタスク一覧を返し、ロックが取れないならNotSupportedExceptionを投げる。
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
Monitor.TryEnter(_tasksLock, ref lockTaken);
|
||||
if (lockTaken)
|
||||
{
|
||||
return _tasks.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken) Monitor.Exit(_tasksLock);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected override void QueueTask(Task task)
|
||||
{
|
||||
//Taskを追加した上で、同時実行スレッド数を超えていなければNotifyThreadPoolOfPendingWorkへ進む。
|
||||
//NotifyThreadPoolOfPendingWorkは、Taskが空になるまで実行を続けるある種のループ構造となるので、これが何個同時に走るかをコントロールすることで、実質的にスレッドプールの同時処理上限をコントロールできる。
|
||||
lock (_tasksLock)
|
||||
{
|
||||
_tasks.AddLast(task);
|
||||
if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism)
|
||||
{
|
||||
_delegatesQueuedOrRunning++;
|
||||
NotifyThreadPoolOfPendingWork();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void NotifyThreadPoolOfPendingWork()
|
||||
{
|
||||
ThreadPool.UnsafeQueueUserWorkItem(_ =>
|
||||
{
|
||||
Task? item;
|
||||
lock (_tasksLock)
|
||||
{
|
||||
//タスクが空になったら_tasks.Firstはnull
|
||||
item = _tasks.First?.Value;
|
||||
if (item is null)
|
||||
{
|
||||
//空になったらこの一連のNotifyThreadPoolOfPendingWorkのループを終了し、ループの数をデクリメントする。
|
||||
_delegatesQueuedOrRunning--;
|
||||
return;
|
||||
}
|
||||
_tasks.RemoveFirst();
|
||||
}
|
||||
|
||||
base.TryExecuteTask(item);
|
||||
NotifyThreadPoolOfPendingWork();
|
||||
}, null);
|
||||
}
|
||||
|
||||
|
||||
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
|
||||
{
|
||||
//インライン実行については、現在回しているループの数が上限以下であれば、空きがあるということで実行する。そうではない場合はfalseで断る。
|
||||
if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism)
|
||||
{
|
||||
if (taskWasPreviouslyQueued)
|
||||
{
|
||||
if (TryDequeue(task))
|
||||
{
|
||||
return base.TryExecuteTask(task);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.TryExecuteTask(task);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool TryDequeue(Task task)
|
||||
{
|
||||
lock (_tasksLock)
|
||||
{
|
||||
return _tasks.Remove(task);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
52
Program.cs
Normal file
52
Program.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using Encoder.Lib;
|
||||
|
||||
namespace Encoder;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var inputDirectory = new DirectoryInfo("input");
|
||||
var outputDirectory = new DirectoryInfo("output");
|
||||
|
||||
if (!inputDirectory.Exists)
|
||||
{
|
||||
inputDirectory.Create();
|
||||
}
|
||||
|
||||
if (!outputDirectory.Exists)
|
||||
{
|
||||
outputDirectory.Create();
|
||||
}
|
||||
|
||||
var coreCount = (int) Math.Max(Environment.ProcessorCount / 2, 1);
|
||||
var limitedTaskScheduler = new LimitedConcurrencyLevelTaskScheduler(coreCount);
|
||||
var limitedTaskFactory = new TaskFactory(limitedTaskScheduler);
|
||||
var tasks = new List<Task>();
|
||||
|
||||
foreach (var file in inputDirectory.EnumerateFiles())
|
||||
{
|
||||
var task = limitedTaskFactory.StartNew(async () => {
|
||||
// detect volumes.
|
||||
var detectWorker = new DetectWorker("./", file.FullName, "-vn -af volumedetect");
|
||||
await detectWorker.Run().ConfigureAwait(false);
|
||||
if (string.IsNullOrEmpty(detectWorker.MaxVolume) || string.IsNullOrWhiteSpace(detectWorker.MeanVolume))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var maxVolumeF = float.Parse(detectWorker.MaxVolume.Substring(0, detectWorker.MaxVolume.Length - 2));
|
||||
var meanVolumeF = float.Parse(detectWorker.MeanVolume.Substring(0, detectWorker.MeanVolume.Length - 2));
|
||||
var gainVolume = $"{Math.Round(meanVolumeF * 10 / 20 * -1)}dB";
|
||||
var outputFile = Path.ChangeExtension(Path.Combine(outputDirectory.FullName, file.Name), "m4a");
|
||||
|
||||
// encoding
|
||||
EncodeWorker worker = new EncodeWorker("./", file.FullName, outputFile, $"-vn -ac 2 -c:a aac -b:a 256k -ar 48000 -af volume={gainVolume} -f mp4");
|
||||
await worker.Run().ConfigureAwait(false);
|
||||
});
|
||||
tasks.Add(task.Unwrap());
|
||||
}
|
||||
|
||||
Task.WhenAll(tasks.ToArray()).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user