I have been searching the web for some time for a solid FFmpeg wrapper for C#/.NET. But I have yet to come up with something useful. I have found the following three projects, but all of them apears to be dead in early alpha stage.
FFmpeg.NET
ffmpeg-sharp
FFLIB.NET
So my question is if anyone knows of a wrapper project that is more mature?
I am not looking for a full transcoding engine with job queues and more.
Just a simple wrapper so I do not have to make a command line call and then parse the console output, but can make method calls and use eventlisteners for progress.
And please feel free to mention any active projects, even if they are stil in the early stages.
This is a wrapper of my own: https://github.com/AydinAdn/MediaToolkit
MediaToolkit can:
Convert video files into various other video formats.
Perform video transcoding tasks.
Options configurable: Bit rate, Frame rate, Resolution / size, Aspect ratio, Duration of video
Perform audio transcoding tasks.
Options configurable: Audio sample rate
Convert video to physical formats using FILM, PAL or NTSC tv standards
Mediums include: DVD, DV, DV50, VCD, SVCD
I'm updating it as I go along, and you're welcome to use it, you can also install it using the Package Manager Console.
PM> Install-Package MediaToolkit
After trying several wrappers, I went with this: FFmpeg auto generated unsafe bindings for C#/.NET and Mono.
It's a set of low-level interop bindings for every class in the FFmpeg namespace.
Maybe not as convenient to use as an actual wrapper, but IMO it's the best solution for working with FFmpeg in .Net, if you want to do non-trivial things.
Pros:
Works
Trustworthy - no 3rd party wrapper code to introduce bugs, assuming you trust FFMpeg itself.
It's always updated to the latest version of FFmpeg
Single nuget package for all of the bindings
XML documentation is included but you still can use the online documentation FFmpeg documentation.
Cons:
Low level: You have to know how to work with pointers to c structs.
Requires some work initially to get it working. I suggest to learn from the official examples.
Note: this thread is about using the FFmpeg API, but for some use cases, it's best to simply use ffmpeg.exe's command line interface.
I have used FFmpeg from a ASP.NET / Windows service (.NET) application.
But I ended up using the command-line, without parsing the console.
By using this - I had an easy way to control - updates of FFmpeg and running multiple conversions on multiple Cores.
Try this, I think I might have written something you can use for a simple wrapper.
http://jasonjano.wordpress.com/2010/02/09/a-simple-c-wrapper-for-ffmpeg/
You can use this nuget package:
I know that you asked about mature project, but i haven't seen any project fullfilling my expectaction so i decided to make my own.
You can easily queue conversions and run it parallel, methods to convert media to different formats, send your own arguments to ffmpeg and parse output from ffmpeg + event listener with current progress.
Install-Package Xabe.FFmpeg
I'm trying to make easy to use, cross-platform FFmpeg wrapper.
You can find more information about this at https://xabe.net/product/xabe_ffmpeg/
More info here: https://xabe.net/product/xabe_ffmpeg/#documentation
Conversion is simple:
IConversionResult result = await Conversion.ToMp4(Resources.MkvWithAudio, output).Start();
If you want progress:
IConversion conversion = Conversion.ToMp4(Resources.MkvWithAudio, output);
conversion.OnProgress += (duration, length) => { currentProgress = duration; }
await conversion.Start();
I'm playing around with an ffmpeg wrapper library called MediaHandler Pro from
http://www.mediasoftpro.com
seems promising so far.
I have been researching the same thing and originally used MediaToolKit (mentioned in another answer) which worked great for conversions but now I need something a bit more robust.
One option that seems mature and still active is:
https://github.com/hudl/HudlFfmpeg
Which you can read more about here:
http://public.hudl.com/bits/archives/2014/08/15/announcing-hudlffmpeg-a-c-framework-to-make-ffmpeg-interaction-simple/
Another option, which may not suit many cases, is to invoke the exe directly from your c# code:
http://www.codeproject.com/Articles/774093/Another-FFmpeg-exe-Csharp-Wrapper
There is another simple one here: http://ivolo.mit.edu/post/Metamorph-Convert-Audio-Video-to-Any-Format-on-Windows-Linux-and-Mac.aspx
Here ya go... Most of this code is 2+ years old so missing a lot of asynchronous stuff, and using an outdated naming convention. Running in a production enviornment for quite some time
~ JT
internal static class FFMpegArgUtils
{
public static string GetEncodeVideoFFMpegArgs(string sSourceFile, MP4Info objMp4Info, double nMbps, int iWidth, int iHeight, bool bIncludeAudio, string sOutputFile)
{
//Ensure file contains a video stream, otherwise this command will fail
if (objMp4Info != null && objMp4Info.VideoStreamCount == 0)
{
throw new Exception("FFMpegArgUtils::GetEncodeVideoFFMpegArgs - mp4 does not contain a video stream");
}
int iBitRateInKbps = (int)(nMbps * 1000);
StringBuilder sbArgs = new StringBuilder();
sbArgs.Append(" -y -threads 2 -i \"" + sSourceFile + "\" -strict -2 "); // 0 tells it to choose how many threads to use
if (bIncludeAudio == true)
{
//sbArgs.Append(" -acodec libmp3lame -ab 96k");
sbArgs.Append(" -acodec aac -ar 44100 -ab 96k");
}
else
{
sbArgs.Append(" -an");
}
sbArgs.Append(" -vcodec libx264 -level 41 -r 15 -crf 25 -g 15 -keyint_min 45 -bf 0");
//sbArgs.Append(" -vf pad=" + iWidth + ":" + iHeight + ":" + iVideoOffsetX + ":" + iVideoOffsetY);
sbArgs.Append(String.Format(" -vf \"scale=iw*min({0}/iw\\,{1}/ih):ih*min({0}/iw\\,{1}/ih),pad={0}:{1}:({0}-iw)/2:({1}-ih)/2\"",iWidth, iHeight));
//Output File
sbArgs.Append(" \"" + sOutputFile + "\"");
return sbArgs.ToString();
}
public static string GetEncodeAudioFFMpegArgs(string sSourceFile, string sOutputFile)
{
var args = String.Format(" -y -threads 2 -i \"{0}\" -strict -2 -acodec aac -ar 44100 -ab 96k -vn \"{1}\"", sSourceFile, sOutputFile);
return args;
//return GetEncodeVideoFFMpegArgs(sSourceFile, null, .2, 854, 480, true, sOutputFile);
//StringBuilder sbArgs = new StringBuilder();
//int iWidth = 854;
//int iHeight = 480;
//sbArgs.Append(" -y -i \"" + sSourceFile + "\" -strict -2 "); // 0 tells it to choose how many threads to use
//sbArgs.Append(" -acodec aac -ar 44100 -ab 96k");
//sbArgs.Append(" -vcodec libx264 -level 41 -r 15 -crf 25 -g 15 -keyint_min 45 -bf 0");
//sbArgs.Append(String.Format(" -vf \"scale=iw*min({0}/iw\\,{1}/ih):ih*min({0}/iw\\,{1}/ih),pad={0}:{1}:({0}-iw)/2:({1}-ih)/2\"", iWidth, iHeight));
//sbArgs.Append(" \"" + sOutputFile + "\"");
//return sbArgs.ToString();
}
}
internal class CreateEncodedVideoCommand : ConsoleCommandBase
{
public event ProgressEventHandler OnProgressEvent;
private string _sSourceFile;
private string _sOutputFolder;
private double _nMaxMbps;
public double BitrateInMbps
{
get { return _nMaxMbps; }
}
public int BitrateInKbps
{
get { return (int)Math.Round(_nMaxMbps * 1000); }
}
private int _iOutputWidth;
private int _iOutputHeight;
private bool _bIsConverting = false;
//private TimeSpan _tsDuration;
private double _nPercentageComplete;
private string _sOutputFile;
private string _sOutputFileName;
private bool _bAudioEnabled = true;
private string _sFFMpegPath;
private string _sExePath;
private string _sArgs;
private MP4Info _objSourceInfo;
private string _sOutputExt;
/// <summary>
/// Encodes an MP4 to the specs provided, quality is a value from 0 to 1
/// </summary>
/// <param name="nQuality">A value from 0 to 1</param>
///
public CreateEncodedVideoCommand(string sSourceFile, string sOutputFolder, string sFFMpegPath, double nMaxBitrateInMbps, MP4Info objSourceInfo, int iOutputWidth, int iOutputHeight, string sOutputExt)
{
_sSourceFile = sSourceFile;
_sOutputFolder = sOutputFolder;
_nMaxMbps = nMaxBitrateInMbps;
_objSourceInfo = objSourceInfo;
_iOutputWidth = iOutputWidth;
_iOutputHeight = iOutputHeight;
_sFFMpegPath = sFFMpegPath;
_sOutputExt = sOutputExt;
}
public void SetOutputFileName(string sOutputFileName)
{
_sOutputFileName = sOutputFileName;
}
public override void Execute()
{
try
{
_bIsConverting = false;
string sFileName = _sOutputFileName != null ? _sOutputFileName : Path.GetFileNameWithoutExtension(_sSourceFile) + "_" + _iOutputWidth + "." + _sOutputExt;
_sOutputFile = _sOutputFolder + "\\" + sFileName;
_sExePath = _sFFMpegPath;
_sArgs = FFMpegArgUtils.GetEncodeVideoFFMpegArgs(_sSourceFile, _objSourceInfo,_nMaxMbps, _iOutputWidth, _iOutputHeight, _bAudioEnabled, _sOutputFile);
InternalExecute(_sExePath, _sArgs);
}
catch (Exception objEx)
{
DispatchException(objEx);
}
}
public override string GetCommandInfo()
{
StringBuilder sbInfo = new StringBuilder();
sbInfo.AppendLine("CreateEncodeVideoCommand");
sbInfo.AppendLine("Exe: " + _sExePath);
sbInfo.AppendLine("Args: " + _sArgs);
sbInfo.AppendLine("[ConsoleOutput]");
sbInfo.Append(ConsoleOutput);
sbInfo.AppendLine("[ErrorOutput]");
sbInfo.Append(ErrorOutput);
return base.GetCommandInfo() + "\n" + sbInfo.ToString();
}
protected override void OnInternalCommandComplete(int iExitCode)
{
DispatchCommandComplete( iExitCode == 0 ? CommandResultType.Success : CommandResultType.Fail);
}
override protected void OnOutputRecieved(object sender, ProcessOutputEventArgs objArgs)
{
//FMPEG out always shows as Error
base.OnOutputRecieved(sender, objArgs);
if (_bIsConverting == false && objArgs.Data.StartsWith("Press [q] to stop encoding") == true)
{
_bIsConverting = true;
}
else if (_bIsConverting == true && objArgs.Data.StartsWith("frame=") == true)
{
//Capture Progress
UpdateProgressFromOutputLine(objArgs.Data);
}
else if (_bIsConverting == true && _nPercentageComplete > .8 && objArgs.Data.StartsWith("frame=") == false)
{
UpdateProgress(1);
_bIsConverting = false;
}
}
override protected void OnProcessExit(object sender, ProcessExitedEventArgs args)
{
_bIsConverting = false;
base.OnProcessExit(sender, args);
}
override public void Abort()
{
if (_objCurrentProcessRunner != null)
{
//_objCurrentProcessRunner.SendLineToInputStream("q");
_objCurrentProcessRunner.Dispose();
}
}
#region Helpers
//private void CaptureSourceDetailsFromOutput()
//{
// String sInputStreamInfoStartLine = _colErrorLines.SingleOrDefault(o => o.StartsWith("Input #0"));
// int iStreamInfoStartIndex = _colErrorLines.IndexOf(sInputStreamInfoStartLine);
// if (iStreamInfoStartIndex >= 0)
// {
// string sDurationInfoLine = _colErrorLines[iStreamInfoStartIndex + 1];
// string sDurantionTime = sDurationInfoLine.Substring(12, 11);
// _tsDuration = VideoUtils.GetDurationFromFFMpegDurationString(sDurantionTime);
// }
//}
private void UpdateProgressFromOutputLine(string sOutputLine)
{
int iTimeIndex = sOutputLine.IndexOf("time=");
int iBitrateIndex = sOutputLine.IndexOf(" bitrate=");
string sCurrentTime = sOutputLine.Substring(iTimeIndex + 5, iBitrateIndex - iTimeIndex - 5);
double nCurrentTimeInSeconds = double.Parse(sCurrentTime);
double nPercentageComplete = nCurrentTimeInSeconds / _objSourceInfo.Duration.TotalSeconds;
UpdateProgress(nPercentageComplete);
//Console.WriteLine("Progress: " + _nPercentageComplete);
}
private void UpdateProgress(double nPercentageComplete)
{
_nPercentageComplete = nPercentageComplete;
if (OnProgressEvent != null)
{
OnProgressEvent(this, new ProgressEventArgs( _nPercentageComplete));
}
}
#endregion
//public TimeSpan Duration { get { return _tsDuration; } }
public double Progress { get { return _nPercentageComplete; } }
public string OutputFile { get { return _sOutputFile; } }
public bool AudioEnabled
{
get { return _bAudioEnabled; }
set { _bAudioEnabled = value; }
}
}
public abstract class ConsoleCommandBase : CommandBase, ICommand
{
protected ProcessRunner _objCurrentProcessRunner;
protected List<String> _colOutputLines;
protected List<String> _colErrorLines;
private int _iExitCode;
public ConsoleCommandBase()
{
_colOutputLines = new List<string>();
_colErrorLines = new List<string>();
}
protected void InternalExecute(string sExePath, string sArgs)
{
InternalExecute(sExePath, sArgs, null, null, null);
}
protected void InternalExecute(string sExePath, string sArgs, string sDomain, string sUsername, string sPassword)
{
try
{
if (_objCurrentProcessRunner == null || _bIsRunning == false)
{
StringReader objStringReader = new StringReader(string.Empty);
_objCurrentProcessRunner = new ProcessRunner(sExePath, sArgs);
_objCurrentProcessRunner.SetCredentials(sDomain, sUsername, sPassword);
_objCurrentProcessRunner.OutputReceived += new ProcessOutputEventHandler(OnOutputRecieved);
_objCurrentProcessRunner.ProcessExited += new ProcessExitedEventHandler(OnProcessExit);
_objCurrentProcessRunner.Run();
_bIsRunning = true;
_bIsComplete = false;
}
else
{
DispatchException(new Exception("Processor Already Running"));
}
}
catch (Exception objEx)
{
DispatchException(objEx);
}
}
protected virtual void OnOutputRecieved(object sender, ProcessOutputEventArgs args)
{
try
{
if (args.Error == true)
{
_colErrorLines.Add(args.Data);
//Console.WriteLine("Error: " + args.Data);
}
else
{
_colOutputLines.Add(args.Data);
//Console.WriteLine(args.Data);
}
}
catch (Exception objEx)
{
DispatchException(objEx);
}
}
protected virtual void OnProcessExit(object sender, ProcessExitedEventArgs args)
{
try
{
Console.Write(ConsoleOutput);
_iExitCode = args.ExitCode;
_bIsRunning = false;
_bIsComplete = true;
//Some commands actually fail to succeed
//if(args.ExitCode != 0)
//{
// DispatchException(new Exception("Command Failed: " + this.GetType().Name + "\nConsole: " + ConsoleOutput + "\nConsoleError: " + ErrorOutput));
//}
OnInternalCommandComplete(_iExitCode);
if (_objCurrentProcessRunner != null)
{
_objCurrentProcessRunner.Dispose();
_objCurrentProcessRunner = null;
}
}
catch (Exception objEx)
{
DispatchException(objEx);
}
}
abstract protected void OnInternalCommandComplete(int iExitCode);
protected string JoinLines(List<String> colLines)
{
StringBuilder sbOutput = new StringBuilder();
colLines.ForEach( o => sbOutput.AppendLine(o));
return sbOutput.ToString();
}
#region Properties
public int ExitCode
{
get { return _iExitCode; }
}
#endregion
public override string GetCommandInfo()
{
StringBuilder sbCommandInfo = new StringBuilder();
sbCommandInfo.AppendLine("Command: " + this.GetType().Name);
sbCommandInfo.AppendLine("Console Output");
if (_colOutputLines != null)
{
foreach (string sOutputLine in _colOutputLines)
{
sbCommandInfo.AppendLine("\t" + sOutputLine);
}
}
sbCommandInfo.AppendLine("Error Output");
if (_colErrorLines != null)
{
foreach (string sErrorLine in _colErrorLines)
{
sbCommandInfo.AppendLine("\t" + sErrorLine);
}
}
return sbCommandInfo.ToString();
}
public String ConsoleOutput { get { return JoinLines(_colOutputLines); } }
public String ErrorOutput { get { return JoinLines(_colErrorLines);} }
}
CommandBase : ICommand
{
protected IDedooseContext _context;
protected Boolean _bIsRunning = false;
protected Boolean _bIsComplete = false;
#region Custom Events
public event CommandCompleteEventHandler OnCommandComplete;
event CommandCompleteEventHandler ICommand.OnCommandComplete
{
add { if (OnCommandComplete != null) { lock (OnCommandComplete) { OnCommandComplete += value; } } else { OnCommandComplete = new CommandCompleteEventHandler(value); } }
remove { if (OnCommandComplete != null) { lock (OnCommandComplete) { OnCommandComplete -= value; } } }
}
public event UnhandledExceptionEventHandler OnCommandException;
event UnhandledExceptionEventHandler ICommand.OnCommandException
{
add { if (OnCommandException != null) { lock (OnCommandException) { OnCommandException += value; } } else { OnCommandException = new UnhandledExceptionEventHandler(value); } }
remove { if (OnCommandException != null) { lock (OnCommandException) { OnCommandException -= value; } } }
}
public event ProgressEventHandler OnProgressUpdate;
event ProgressEventHandler ICommand.OnProgressUpdate
{
add { if (OnProgressUpdate != null) { lock (OnProgressUpdate) { OnProgressUpdate += value; } } else { OnProgressUpdate = new ProgressEventHandler(value); } }
remove { if (OnProgressUpdate != null) { lock (OnProgressUpdate) { OnProgressUpdate -= value; } } }
}
#endregion
protected CommandBase()
{
_context = UnityGlobalContainer.Instance.Context;
}
protected void DispatchCommandComplete(CommandResultType enResult)
{
if (enResult == CommandResultType.Fail)
{
StringBuilder sbMessage = new StringBuilder();
sbMessage.AppendLine("Command Commpleted with Failure: " + this.GetType().Name);
sbMessage.Append(GetCommandInfo());
Exception objEx = new Exception(sbMessage.ToString());
DispatchException(objEx);
}
else
{
if (OnCommandComplete != null)
{
OnCommandComplete(this, new CommandCompleteEventArgs(enResult));
}
}
}
protected void DispatchException(Exception objEx)
{
if (OnCommandException != null)
{
OnCommandException(this, new UnhandledExceptionEventArgs(objEx, true));
}
else
{
_context.Logger.LogException(objEx, MethodBase.GetCurrentMethod());
throw objEx;
}
}
protected void DispatchProgressUpdate(double nProgressRatio)
{
if (OnProgressUpdate != null) { OnProgressUpdate(this, new ProgressEventArgs(nProgressRatio)); }
}
public virtual string GetCommandInfo()
{
return "Not Implemented: " + this.GetType().Name;
}
public virtual void Execute() { throw new NotImplementedException(); }
public virtual void Abort() { throw new NotImplementedException(); }
public Boolean IsRunning { get { return _bIsRunning; } }
public Boolean IsComplete { get { return _bIsComplete; } }
public double GetProgressRatio()
{
throw new NotImplementedException();
}
}
public delegate void CommandCompleteEventHandler(object sender, CommandCompleteEventArgs e);
public interface ICommand
{
event CommandCompleteEventHandler OnCommandComplete;
event UnhandledExceptionEventHandler OnCommandException;
event ProgressEventHandler OnProgressUpdate;
double GetProgressRatio();
string GetCommandInfo();
void Execute();
void Abort();
}
// for the process runner stuff look up ProcessRunner by Roger Knapp
string result = String.Empty;
StreamReader srOutput = null;
var oInfo = new ProcessStartInfo(exePath, parameters)
{
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
var output = string.Empty;
try
{
Process process = System.Diagnostics.Process.Start(oInfo);
output = process.StandardError.ReadToEnd();
process.WaitForExit();
process.Close();
}
catch (Exception)
{
output = string.Empty;
}
return output;
This wrapper will not let the method fall into a loop.
Try this,it worked for me.
I forked FFPMEG.net from codeplex.
Still actively being worked on.
https://github.com/spoiledtechie/FFMpeg.Net
It doesn't use the dlls, but rather the exe. So it tends to be more stable.
See Auto Generated FFmpeg wrapper for C#/.NET and Mono, an awesome project which seems like the only true, complete .NET wrapper for FFmpeg interop out there.
Related
I want to make an auto injection scanner in any given website and I have to use c#.
I tried some things that I found online and none of them worked for me, until i find selenium but i keep getting this error message: "OpenQA.Selenium.ElementNotInteractableException: 'element not interactable", and I have no idea why.
I didn't find anything helpful online and I think the problem may be with selenium.
I tried to find SQL, JS and BASH injections, but the script fails when i try to interact with an input. I am using OWASP juice shop to test my code.
This is my code:
static int _crntTypeOfInjection;
const int ESQL = 0, EJS = 1, EBASH = 2;
static public bool IsImportantInput(string type)
{
bool valid = false;
string[] importantTypes = new string[] { "text", "email", "password", "search", "url" };
foreach (string check in importantTypes)
{
if (type == check)
{
return true;
}
}
return false;
}
public static string getCrntInjection()
{
switch (_crntTypeOfInjection)
{
case ESQL:
return "\' OR 1=1;--";
break;
case EBASH:
return "; echo Test";
break;
case EJS:
return "<img src=\"http:\\\\url.to.file.which\\not.exist\" onerror=alert(\"JS injection success\");>";
break;
}
return "defult";
}
static public bool AttackSuccessful(string normalPage, string InjectedPage, string MainUrl, string afterClickUrl)
{
if (afterClickUrl != MainUrl || InjectedPage.Contains("Internal Server Error") || InjectedPage.Contains("JS injection success") || InjectedPage.Contains("Test"))
{
return true;
}
return false;
}
static public void Injection(string url)
{
string InjectedPage = "", NormalPage = "", AfterClickUrl = "";
var driver = new ChromeDriver("C:\\Users\\nirya\\");
driver.Url = url;
Console.WriteLine(driver.PageSource);
Actions a = new Actions(driver);
foreach (var button in driver.FindElements(By.CssSelector("button")))
{
// INJECTED PAGE
a.MoveByOffset(0, 0).Click().Perform();
foreach (IWebElement input in driver.FindElements(By.TagName("input")))
{
Console.WriteLine(input.Text);
Console.WriteLine(input.TagName);
try
{
if (IsImportantInput(input.GetAttribute("type")))
{
input.Click(); // *** HERE IS THE PROBLEM ***
input.Clear();
input.SendKeys(getCrntInjection());
}
}
catch (NoSuchElementException)
{
continue;
}
}
button.Click();
InjectedPage = driver.PageSource;
AfterClickUrl = driver.Url;
driver.Navigate().Back();
// NORMAL PAGE
a.MoveByOffset(0, 0).Click().Perform();
foreach (IWebElement input in driver.FindElements(By.CssSelector("input")))
{
try
{
if (IsImportantInput(input.GetAttribute("type")))
{
input.Clear();
input.SendKeys("normal");
}
}
catch (NoSuchElementException)
{
continue;
}
}
button.Click();
NormalPage = driver.PageSource;
driver.Navigate().Back();
if (AttackSuccessful(NormalPage, InjectedPage, url, AfterClickUrl))
{
// add to database
}
}
}
static void Main(string[] args)
{
Injection("http://localhost:3000/#/login");
}
Is there a problem with my code? Or is there another library that i can use instead?
I'm updating a working app that targeted API 9 (Pie) to API 33 (Tiramisu) and the camera always returns Result.Cancelled to the OnActivityResult code. I'm using Visual Studio 2022 Version 17.3.6 on a Windows 11 pro machine. This is my camera code:
camerabutton.Click += (sender, evt) =>
{
var cameraispresent = checkCameraHardware(this);
if (cameraispresent)
{
try
{
CreateDirectoryForPictures();
MySubjectInfo.Name = MySubjectInfo.Name.Replace(",", "");
MySubjectInfo.Name = MySubjectInfo.Name.Replace(" ", "");
MySubjectInfo.Name = MySubjectInfo.Name.Replace(".", "");
var filename = MySubjectInfo.Name + ".jpg";
Intent intent = new Intent(MediaStore.ActionImageCapture);
App._file = new File(App._dir, String.Format(filename, Guid.NewGuid()));
intent.PutExtra(MediaStore.ExtraOutput, Uri.FromFile(App._file));
StartActivityForResult(intent, TakePictureRequestCode);
}
catch (Exception e)
{
var result = e.Message;
};
}
};
The create directory code, this code was also updated to reflect changes in API 33, however I can't find the folder on my test device via file explorer where the photos should be stored yet App._dir.Exists() returns true saying it's there:
private void CreateDirectoryForPictures()
{
int version = (int)Android.OS.Build.VERSION.SdkInt;
var root = "";
if (Android.OS.Environment.IsExternalStorageEmulated)
{
root = Android.OS.Environment.ExternalStorageDirectory.ToString();
}
else
{
root = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyPictures);
}
if (version >= Convert.ToInt32(BuildVersionCodes.Q))
{
App._dir = new File(root + "/PhotoManager");
}
else
{
App._dir = new File(
Environment.GetExternalStoragePublicDirectory(
Environment.DirectoryPictures), "PhotoManager");
}
if (!App._dir.Exists())
{
App._dir.Mkdirs();
}
}
This is the app class where I store the data:
public static class App
{
public static File _file;
public static File _dir;
public static Bitmap bitmap;
}
This is the OnActivityResult1 code, I left my API 9 code in there commented out so you can see where I was and where I'm going. With API 9 I needed to resize the picture to scale it down to 160 X 100 as these photos are used for ID cards, I commented that code out until I can figure out why the camera intent is returning null:
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (requestCode == TakePictureRequestCode && resultCode == Result.Ok)
{
//Intent mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
//Uri contentUri = Uri.FromFile(App._file);
//mediaScanIntent.SetData(contentUri);
//SendBroadcast(mediaScanIntent);
Bitmap photo = (Bitmap)data.GetByteArrayExtra("data");
App.bitmap = photo;
//int height = 160; //Resources.DisplayMetrics.HeightPixels;
//int width = 100; //MyImageView.Height;
//App.bitmap = App._file.Path.LoadAndResizeBitmap(width, height);
if (App.bitmap != null)
{
MyImageView.SetImageBitmap(App.bitmap);
Bitmap bitmap = App.bitmap;
var filePath = App._file.AbsolutePath;
var stream = new System.IO.FileStream(filePath, System.IO.FileMode.Create);
bitmap.Compress(Bitmap.CompressFormat.Jpeg, 100, stream);
stream.Close();
bitmap = null;
App.bitmap = null;
PictureTaken = true;
}
// Dispose of the Java side bitmap.
GC.Collect();
}
}
So what am I doing wrong?
You cannot use Uri.FromFile anymore since Android 7.
Use FileProvider to serve a writable uri for the camera app.
Example: EnsureBluetoothEnabled called from a permission request
Setup
activityResultCallback = new ActivityResultCallback();
activityResultCallback.OnActivityResultCalled += ActivityResultCallback_ActivityResultCalled;
activityResultLauncher = RegisterForActivityResult(new ActivityResultContracts.StartActivityForResult(), activityResultCallback);
private void EnsureBluetoothEnabled()
{
if ((bluetoothAdapter != null) && (!bluetoothAdapter.IsEnabled))
activityResultLauncher.Launch(new Intent(BluetoothAdapter.ActionRequestEnable));
}
Handler:
private void ActivityResultCallback_ActivityResultCalled(object sender, ActivityResult result)
{
if (result.ResultCode == (int)Result.Ok)
{
if (bluetoothAdapter.State == State.On)
ShowBluetoothConfirmationDialog(true);
}
else if (result.ResultCode == (int)Result.Canceled)
ShowBluetoothConfirmationDialog(false);
}
ActivityResultCallback class
public class ActivityResultCallback : Java.Lang.Object, IActivityResultCallback
{
public EventHandler<ActivityResult> OnActivityResultCalled;
public void OnActivityResult(Java.Lang.Object result)
{
ActivityResult activityResult = result as ActivityResult;
OnActivityResultCalled?.Invoke(this, activityResult);
}
}
I'm trying to implement a simple file-logger object, with the possibility to truncate the file when its size reaches a threshold.
I am using a StreamWriter, which gets written to at each call of the method Log(). In order to decide when to truncate, I am checking the StreamWriter.BaseStream.Length property before each write and, if it is bigger than the threshold, I close the StreamWriter, create a new file and open the StreamWriter on that file.
For example, if I set the threshold to 10Mb files, it will create a new file each 10Mb of written data.
Under normal load (let's say 3-4 seconds between calls to Log()), everything works as it should. However, the product which is going to use this logger will work with lots of data and required logging every 1 second, even less.
The problem is that the logger seems to completely ignore the creation of the new file(and opening the new stream), failing to truncate it and keeps writing to the existing stream.
I also tried to manually compute the stream's length, hoping it would be a problem with the stream, but it does not work.
I have found out that going step by step with the debugger makes it work correctly, but it does not solve my problem. Logging each second seems to make the program skip the UpdateFile() method entirely.
public class Logger
{
private static Logger _logger;
private const string LogsDirectory = "Logs";
private StreamWriter _streamWriter;
private string _path;
private readonly bool _truncate;
private readonly int _maxSizeMb;
private long _currentSize;
//===========================================================//
public static void Set(string filename, bool truncate = false, int maxSizeMb = 10)
{
if (_logger == null)
{
if (filename.Contains('_'))
{
throw new Exception("Filename cannot contain the _ character!");
}
if (filename.Contains('.'))
{
throw new Exception("The filename must not include the extension");
}
_logger = new Logger(filename, truncate, maxSizeMb);
}
}
//===========================================================//
public static void Log(string message, LogType logType = LogType.Info)
{
_logger?.InternalLog(message, logType);
}
//===========================================================//
public static void LogException(Exception ex)
{
_logger?.InternalLogException(ex);
}
//===========================================================//
private Logger(string filename, bool truncate = false, int maxSizeMb = 10)
{
_path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, LogsDirectory, $"{filename}_{DateTimeToPrefix(DateTime.Now)}.log");
if (CheckForExistingLogs())
{
_path = GetLatestLogFilename();
}
_truncate = truncate;
_maxSizeMb = maxSizeMb;
_streamWriter = new StreamWriter(File.Open(_path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite));
_currentSize = _streamWriter.BaseStream.Length;
}
//===========================================================//
private bool CheckForExistingLogs()
{
var directory = Path.GetDirectoryName(_path);
var filename = Path.GetFileNameWithoutExtension(_path);
if (filename.Contains('_'))
{
filename = filename.Split('_').First();
}
return new DirectoryInfo(directory).GetFiles().Any(x => x.Name.ToLower().Contains(filename.ToLower()));
}
//===========================================================//
private string GetLatestLogFilename()
{
var directory = Path.GetDirectoryName(_path);
var filename = Path.GetFileNameWithoutExtension(_path);
if (filename.Contains('_'))
{
filename = filename.Split('_').First();
}
var files = new DirectoryInfo(directory).GetFiles().Where(x => x.Name.ToLower().Contains(filename.ToLower()));
files = files.OrderBy(x => PrefixToDateTime(x.Name.Split('_').Last()));
return files.Last().FullName;
}
//===========================================================//
private void UpdateFile()
{
_streamWriter.Flush();
_streamWriter.Close();
_streamWriter.Dispose();
_streamWriter = StreamWriter.Null;
_path = GenerateNewFilename();
_streamWriter = new StreamWriter(File.Open(_path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite));
_currentSize = _streamWriter.BaseStream.Length;
}
//===========================================================//
private string GenerateNewFilename()
{
var directory = Path.GetDirectoryName(_path);
var oldFilename = Path.GetFileNameWithoutExtension(_path);
if (oldFilename.Contains('_'))
{
oldFilename = oldFilename.Split('_').First();
}
var newFilename = $"{oldFilename}_{DateTimeToPrefix(DateTime.Now)}.log";
return Path.Combine(directory, newFilename);
}
//===========================================================//
private static string DateTimeToPrefix(DateTime dateTime)
{
return dateTime.ToString("yyyyMMddHHmm");
}
//===========================================================//
private static DateTime PrefixToDateTime(string prefix)
{
var year = Convert.ToInt32(string.Join("", prefix.Take(4)));
var month = Convert.ToInt32(string.Join("", prefix.Skip(4).Take(2)));
var day = Convert.ToInt32(string.Join("", prefix.Skip(6).Take(2)));
var hour = Convert.ToInt32(string.Join("", prefix.Skip(8).Take(2)));
var minute = Convert.ToInt32(string.Join("", prefix.Skip(10).Take(2)));
return new DateTime(year, month, day, hour, minute, 0);
}
//===========================================================//
private int ConvertSizeToMb()
{
return Convert.ToInt32(Math.Truncate(_currentSize / 1024f / 1024f));
}
//===========================================================//
public void InternalLog(string message, LogType logType = LogType.Info)
{
if (_truncate && ConvertSizeToMb() >= _maxSizeMb)
{
UpdateFile();
}
var sendMessage = string.Empty;
switch (logType)
{
case LogType.Error:
{
sendMessage += "( E ) ";
break;
}
case LogType.Warning:
{
sendMessage += "( W ) ";
break;
}
case LogType.Info:
{
sendMessage += "( I ) ";
break;
}
}
sendMessage += $"{DateTime.Now:dd.MM.yyyy HH:mm:ss}: {message}";
_streamWriter.WriteLine(sendMessage);
_streamWriter.Flush();
_currentSize += Encoding.ASCII.GetByteCount(sendMessage);
Console.WriteLine(_currentSize);
}
//===========================================================//
public void InternalLogException(Exception ex)
{
if (_truncate && ConvertSizeToMb() >= _maxSizeMb)
{
UpdateFile();
}
var sendMessage = $"( E ) {DateTime.Now:dd.MM.yyyy HH:mm:ss}: {ex.Message}{Environment.NewLine}{ex.StackTrace}";
_streamWriter.WriteLine(sendMessage);
_streamWriter.Flush();
_currentSize += Encoding.ASCII.GetByteCount(sendMessage);
}
}
Usage example:
private static void Main(string[] args)
{
Logger.Set("Log", true, 10);
while (true)
{
Logger.Log("anything");
}
}
Have you ever encountered such a problem before? How can it be solved? Thanks :)
I don't know how much data your application writes to the log each minute. But if the amount is more than 10MB then the method DateTimeToPrefix will return the same name for a second call inside a minute interval. (Well at least for me this is what happens with the code included in the Main method).
I changed the ToString() to include also the seconds and this gives correct amount of data written in the expected files.
private static string DateTimeToPrefix(DateTime dateTime)
{
return dateTime.ToString("yyyyMMddHHmmss");
}
Your code looks "fine", so I'm guessing the issue is being caused by multiple threads accessing the InternalLog method at once. Your code is not thread safe as it does not use any locking mechanisms. The easiest and probably totally sufficient solution for your project is to add a locking object at class level:
private readonly object _lock = new object();
then wrap your whole InternalLog method in a lock(_lock) statement:
public void InternalLog(string message, LogType logType = LogType.Info)
{
lock(_lock)
{
// your existing code
}
}
This is not a perfect solution and could cause a bottleneck especially as you are flushing the StreamWriter in every call to InternalLog. But for now it should be perfectly fine!
Using .NET 4, wpf c#, I am passing method return values and parameters between two processes.
As I need the connection open and active a all times, I have tried my best to minimize the code that is recurring (within the loop) but unless I put this whole code inside the loop it did not succeed (after the first transfer the connection to server was closed), so as it is here, it does work repeatedly.
I was wondering first is this the way it should be coded, all the process including the new instance, dispose, close... within the loop?
Is the only available datatype for inter-process communication to pass as a string (inefficient)?
public void client()
{
for (int i = 0; i < 2; i++)
{
System.IO.Pipes.NamedPipeClientStream pipeClient =
new System.IO.Pipes.NamedPipeClientStream(".", "testpipe",
System.IO.Pipes.PipeDirection.InOut, System.IO.Pipes.PipeOptions.None);
if (pipeClient.IsConnected != true)
{
pipeClient.Connect(550);
}
System.IO.StreamReader sr = new System.IO.StreamReader(pipeClient);
System.IO.StreamWriter sw = new System.IO.StreamWriter(pipeClient);
string status;
status = sr.ReadLine();
if (status == "Waiting")
{
try
{
sw.WriteLine("param1fileName.cs,33" + i);
sw.Flush();
pipeClient.Close();
}
catch (Exception ex) { throw ex; }
}
}
}
public string server()
{
NamedPipeServerStream pipeServer = null;
do
{
try
{
pipeServer = new NamedPipeServerStream("testpipe", PipeDirection.InOut, 4);
StreamReader sr = new StreamReader(pipeServer);
StreamWriter sw = new StreamWriter(pipeServer);
System.Threading.Thread.Sleep(100);
pipeServer.WaitForConnection();
string test;
sw.WriteLine("Waiting");
sw.Flush();
pipeServer.WaitForPipeDrain();
test = sr.ReadLine();
if (!string.IsNullOrEmpty(test))
try
{
System.Windows.Application.Current.Dispatcher.Invoke(new Action(() => MbxTw.Show(Convert.ToInt32(test.Split(',')[1]), test.Split(',')[0], "method()", "Warning!! - " + "content")), System.Windows.Threading.DispatcherPriority.Normal);
}
catch (Exception e)
{
}
}
catch (Exception ex) {
throw ex; }
finally
{
pipeServer.WaitForPipeDrain();
if (pipeServer.IsConnected) { pipeServer.Disconnect(); }
}
} while (true);
}
after a thorough research on inter process communication I have changed the approach from using Named pipes to Memory mapped files,
as it is all rounds winner, no need to recreate it and faster
I present the ultimate partitioned-global-app-inter-process communication
any thoughts on the code will be greatly appreciated!
public class MMFinterComT
{
public EventWaitHandle flagCaller1, flagCaller2, flagReciver1, flagReciver2;
private System.IO.MemoryMappedFiles.MemoryMappedFile mmf;
private System.IO.MemoryMappedFiles.MemoryMappedViewAccessor accessor;
public virtual string DepositChlName { get; set; }
public virtual string DepositThrdName { get; set; }
public virtual int DepositSize { get; set; }
private System.Threading.Thread writerThread;
private bool writerThreadRunning;
public int ReadPosition { get; set; }
public List<string> statusSet;
private int writePosition;
public int WritePosition
{
get { return writePosition; }
set
{
if (value != writePosition)
{
this.writePosition = value;
this.accessor.Write(WritePosition + READ_CONFIRM_OFFSET, true);
}
}
}
private List<byte[]> dataToSend;
private const int DATA_AVAILABLE_OFFSET = 0;
private const int READ_CONFIRM_OFFSET = DATA_AVAILABLE_OFFSET + 1;
private const int DATA_LENGTH_OFFSET = READ_CONFIRM_OFFSET + 1;
private const int DATA_OFFSET = DATA_LENGTH_OFFSET + 10;
public IpcMMFinterComSF.MMFinterComTStatus IntercomStatus;
public MMFinterComT(string ctrIpcChannelNameStr, string ctrIpcThreadName, int ctrMMFSize)
{
this.DepositChlName = ctrIpcChannelNameStr;
this.Deposit Size = ctrMMFSize;
this.DepositThrdName = ctrIpcThreadName;
mmf = MemoryMappedFile.CreateOrOpen(DepositChlName, DepositSize);
accessor = mmf.CreateViewAccessor(0, DepositSize, System.IO.MemoryMappedFiles.MemoryMappedFileAccess.ReadWrite);//if (started)
//smLock = new System.Threading.Mutex(true, IpcMutxName, out locked);
ReadPosition = -1;
writePosition = -1;
this.dataToSend = new List<byte[]>();
this.statusSet = new List<string>();
}
public bool reading;
public byte[] ReadData;
public void StartReader()
{
if (this.IntercomStatus != IpcMMFinterComSF.MMFinterComTStatus._Null || ReadPosition < 0 || writePosition < 0)
return;
this.IntercomStatus = IpcMMFinterComSF.MMFinterComTStatus.PreparingReader;
System.Threading.Thread t = new System.Threading.Thread(ReaderThread);
t.IsBackground = true;
t.Start();
}
private void ReaderThread(object stateInfo)
{
// Checks if there is something to read.
this.IntercomStatus = IpcMMFinterComSF.MMFinterComTStatus.TryingToRead;
this.reading = accessor.ReadBoolean(ReadPosition + DATA_AVAILABLE_OFFSET);
if (this.reading)
{
this.IntercomStatus = IpcMMFinterComSF.MMFinterComTStatus.ReadingData;
// Checks how many bytes to read.
int availableBytes = accessor.ReadInt32(ReadPosition + DATA_LENGTH_OFFSET);
this.ReadData = new byte[availableBytes];
// Reads the byte array.
int read = accessor.ReadArray<byte>(ReadPosition + DATA_OFFSET, this.ReadData, 0, availableBytes);
// Sets the flag used to signal that there aren't available data anymore.
accessor.Write(ReadPosition + DATA_AVAILABLE_OFFSET, false);
// Sets the flag used to signal that data has been read.
accessor.Write(ReadPosition + READ_CONFIRM_OFFSET, true);
this.IntercomStatus = IpcMMFinterComSF.MMFinterComTStatus.FinishedReading;
}
else this.IntercomStatus = IpcMMFinterComSF.MMFinterComTStatus._Null;
}
public void Write(byte[] data)
{
if (ReadPosition < 0 || writePosition < 0)
throw new ArgumentException();
this.statusSet.Add("ReadWrite:-> " + ReadPosition + "-" + writePosition);
lock (this.dataToSend)
this.dataToSend.Add(data);
if (!writerThreadRunning)
{
writerThreadRunning = true;
writerThread = new System.Threading.Thread(WriterThread);
writerThread.IsBackground = true;
writerThread.Name = this.DepositThrdName;
writerThread.Start();
}
}
public void WriterThread(object stateInfo)
{
while (dataToSend.Count > 0 && !this.disposed)
{
byte[] data = null;
lock (dataToSend)
{
data = dataToSend[0];
dataToSend.RemoveAt(0);
}
while (!this.accessor.ReadBoolean(WritePosition + READ_CONFIRM_OFFSET))
System.Threading.Thread.Sleep(133);
// Sets length and write data.
this.accessor.Write(writePosition + DATA_LENGTH_OFFSET, data.Length);
this.accessor.WriteArray<byte>(writePosition + DATA_OFFSET, data, 0, data.Length);
// Resets the flag used to signal that data has been read.
this.accessor.Write(writePosition + READ_CONFIRM_OFFSET, false);
// Sets the flag used to signal that there are data avaibla.
this.accessor.Write(writePosition + DATA_AVAILABLE_OFFSET, true);
}
writerThreadRunning = false;
}
public virtual void Close()
{
if (accessor != null)
{
try
{
accessor.Dispose();
accessor = null;
}
catch { }
}
if (this.mmf != null)
{
try
{
mmf.Dispose();
mmf = null;
}
catch { }
}
disposed = true;
GC.SuppressFinalize(this);
}
private bool disposed;
}
and usage
instaciant once !
public static bool StartCurProjInterCom(IpcAccessorSetting curSrv, int DepoSize)
{
if(CurProjMMF ==null)
CurProjMMF = new MMFinterComT(curSrv.Channel.ToString(), curSrv.AccThreadName.ToString(), DepoSize);
CurProjMMF.flagCaller1 = new EventWaitHandle(false, EventResetMode.ManualReset, CurProjMMF.DepositThrdName);
CurProjMMF.flagCaller2 = new EventWaitHandle(false, EventResetMode.ManualReset, CurProjMMF.DepositThrdName);
CurProjMMF.flagReciver1 = new EventWaitHandle(false, EventResetMode.ManualReset, IpcAccessorThreadNameS.DebuggerThrd.ToString());
CurProjMMF.ReadPosition = curSrv.AccessorSectorsSets.DepoSects.Setter.Read;
CurProjMMF.WritePosition = curSrv.AccessorSectorsSets.DepoSects.Setter.Write;
Console.WriteLine("MMFInterComSetter.ReadPosition " + CurProjMMF.ReadPosition);
Console.WriteLine("MMFInterComSetter.WritePosition " + CurProjMMF.WritePosition);
CurProjMMF.StartReader();
return true;
}
use many
public static void StartADebugerInterComCall(IpcCarier SetterDataObj)
{
IpcAccessorSetting curSrv = new IpcAccessorSetting(IpcMMf.IPChannelS.Debugger, IpcAccessorThreadNameS.DebuggerThrdCurProj, 0, 5000);
StartCurProjInterCom(curSrv, 10000);
var dataW = SetterDataObj.IpcCarierToByteArray();//System.Text.Encoding.UTF8.GetBytes(msg);
CurProjMMF.Write(dataW);
CurProjMMF.flagReciver1.Set();
CurProjMMF.flagCaller1.WaitOne();
CurProjMMF.flagCaller1.Reset();
}
I ve been searching for a while and all that i ve seen some OCR library requests. I would like to know how to implement the purest, easy to install and use OCR library with detailed info for installation into a C# project.
If posible, I just wanna implement it like a usual dll reference...
Example:
using org.pdfbox.pdmodel;
using org.pdfbox.util;
Also a little OCR code example would be nice, such as:
public string OCRFromBitmap(Bitmap Bmp)
{
Bmp.Save(temppath, System.Drawing.Imaging.ImageFormat.Tiff);
string OcrResult = Analyze(temppath);
File.Delete(temppath);
return OcrResult;
}
So please consider that I'm not familiar to OCR projects and give me an answer like talking to a dummy.
Edit:
I guess people misunderstood my request. I wanted to know how to implement those open source OCR libraries to a C# project and how to use them. The link given as dup is not giving answers that I requested at all.
If anyone is looking into this, I've been trying different options and the following approach yields very good results. The following are the steps to get a working example:
Add .NET Wrapper for tesseract to your project. It can be added via NuGet package Install-Package Tesseract(https://github.com/charlesw/tesseract).
Go to the Downloads section of the official Tesseract project (https://code.google.com/p/tesseract-ocr/ EDIT: It's now located here: https://github.com/tesseract-ocr/langdata).
Download the preferred language data, example: tesseract-ocr-3.02.eng.tar.gz English language data for Tesseract 3.02.
Create tessdata directory in your project and place the language data files in it.
Go to Properties of the newly added files and set them to copy on build.
Add a reference to System.Drawing.
From .NET Wrapper repository, in the Samples directory copy the sample phototest.tif file into your project directory and set it to copy on build.
Create the following two files in your project (just to get started):
Program.cs
using System;
using Tesseract;
using System.Diagnostics;
namespace ConsoleApplication
{
class Program
{
public static void Main(string[] args)
{
var testImagePath = "./phototest.tif";
if (args.Length > 0)
{
testImagePath = args[0];
}
try
{
var logger = new FormattedConsoleLogger();
var resultPrinter = new ResultPrinter(logger);
using (var engine = new TesseractEngine(#"./tessdata", "eng", EngineMode.Default))
{
using (var img = Pix.LoadFromFile(testImagePath))
{
using (logger.Begin("Process image"))
{
var i = 1;
using (var page = engine.Process(img))
{
var text = page.GetText();
logger.Log("Text: {0}", text);
logger.Log("Mean confidence: {0}", page.GetMeanConfidence());
using (var iter = page.GetIterator())
{
iter.Begin();
do
{
if (i % 2 == 0)
{
using (logger.Begin("Line {0}", i))
{
do
{
using (logger.Begin("Word Iteration"))
{
if (iter.IsAtBeginningOf(PageIteratorLevel.Block))
{
logger.Log("New block");
}
if (iter.IsAtBeginningOf(PageIteratorLevel.Para))
{
logger.Log("New paragraph");
}
if (iter.IsAtBeginningOf(PageIteratorLevel.TextLine))
{
logger.Log("New line");
}
logger.Log("word: " + iter.GetText(PageIteratorLevel.Word));
}
} while (iter.Next(PageIteratorLevel.TextLine, PageIteratorLevel.Word));
}
}
i++;
} while (iter.Next(PageIteratorLevel.Para, PageIteratorLevel.TextLine));
}
}
}
}
}
}
catch (Exception e)
{
Trace.TraceError(e.ToString());
Console.WriteLine("Unexpected Error: " + e.Message);
Console.WriteLine("Details: ");
Console.WriteLine(e.ToString());
}
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
private class ResultPrinter
{
readonly FormattedConsoleLogger logger;
public ResultPrinter(FormattedConsoleLogger logger)
{
this.logger = logger;
}
public void Print(ResultIterator iter)
{
logger.Log("Is beginning of block: {0}", iter.IsAtBeginningOf(PageIteratorLevel.Block));
logger.Log("Is beginning of para: {0}", iter.IsAtBeginningOf(PageIteratorLevel.Para));
logger.Log("Is beginning of text line: {0}", iter.IsAtBeginningOf(PageIteratorLevel.TextLine));
logger.Log("Is beginning of word: {0}", iter.IsAtBeginningOf(PageIteratorLevel.Word));
logger.Log("Is beginning of symbol: {0}", iter.IsAtBeginningOf(PageIteratorLevel.Symbol));
logger.Log("Block text: \"{0}\"", iter.GetText(PageIteratorLevel.Block));
logger.Log("Para text: \"{0}\"", iter.GetText(PageIteratorLevel.Para));
logger.Log("TextLine text: \"{0}\"", iter.GetText(PageIteratorLevel.TextLine));
logger.Log("Word text: \"{0}\"", iter.GetText(PageIteratorLevel.Word));
logger.Log("Symbol text: \"{0}\"", iter.GetText(PageIteratorLevel.Symbol));
}
}
}
}
FormattedConsoleLogger.cs
using System;
using System.Collections.Generic;
using System.Text;
using Tesseract;
namespace ConsoleApplication
{
public class FormattedConsoleLogger
{
const string Tab = " ";
private class Scope : DisposableBase
{
private int indentLevel;
private string indent;
private FormattedConsoleLogger container;
public Scope(FormattedConsoleLogger container, int indentLevel)
{
this.container = container;
this.indentLevel = indentLevel;
StringBuilder indent = new StringBuilder();
for (int i = 0; i < indentLevel; i++)
{
indent.Append(Tab);
}
this.indent = indent.ToString();
}
public void Log(string format, object[] args)
{
var message = String.Format(format, args);
StringBuilder indentedMessage = new StringBuilder(message.Length + indent.Length * 10);
int i = 0;
bool isNewLine = true;
while (i < message.Length)
{
if (message.Length > i && message[i] == '\r' && message[i + 1] == '\n')
{
indentedMessage.AppendLine();
isNewLine = true;
i += 2;
}
else if (message[i] == '\r' || message[i] == '\n')
{
indentedMessage.AppendLine();
isNewLine = true;
i++;
}
else
{
if (isNewLine)
{
indentedMessage.Append(indent);
isNewLine = false;
}
indentedMessage.Append(message[i]);
i++;
}
}
Console.WriteLine(indentedMessage.ToString());
}
public Scope Begin()
{
return new Scope(container, indentLevel + 1);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
var scope = container.scopes.Pop();
if (scope != this)
{
throw new InvalidOperationException("Format scope removed out of order.");
}
}
}
}
private Stack<Scope> scopes = new Stack<Scope>();
public IDisposable Begin(string title = "", params object[] args)
{
Log(title, args);
Scope scope;
if (scopes.Count == 0)
{
scope = new Scope(this, 1);
}
else
{
scope = ActiveScope.Begin();
}
scopes.Push(scope);
return scope;
}
public void Log(string format, params object[] args)
{
if (scopes.Count > 0)
{
ActiveScope.Log(format, args);
}
else
{
Console.WriteLine(String.Format(format, args));
}
}
private Scope ActiveScope
{
get
{
var top = scopes.Peek();
if (top == null) throw new InvalidOperationException("No current scope");
return top;
}
}
}
}
Here's one: (check out http://hongouru.blogspot.ie/2011/09/c-ocr-optical-character-recognition.html or http://www.codeproject.com/Articles/41709/How-To-Use-Office-2007-OCR-Using-C for more info)
using MODI;
static void Main(string[] args)
{
DocumentClass myDoc = new DocumentClass();
myDoc.Create(#"theDocumentName.tiff"); //we work with the .tiff extension
myDoc.OCR(MiLANGUAGES.miLANG_ENGLISH, true, true);
foreach (Image anImage in myDoc.Images)
{
Console.WriteLine(anImage.Layout.Text); //here we cout to the console.
}
}
I'm using tesseract OCR engine with TessNet2 (a C# wrapper - http://www.pixel-technology.com/freeware/tessnet2/).
Some basic code:
using tessnet2;
...
Bitmap image = new Bitmap(#"u:\user files\bwalker\2849257.tif");
tessnet2.Tesseract ocr = new tessnet2.Tesseract();
ocr.SetVariable("tessedit_char_whitelist", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.,$-/#&=()\"':?"); // Accepted characters
ocr.Init(#"C:\Users\bwalker\Documents\Visual Studio 2010\Projects\tessnetWinForms\tessnetWinForms\bin\Release\", "eng", false); // Directory of your tessdata folder
List<tessnet2.Word> result = ocr.DoOCR(image, System.Drawing.Rectangle.Empty);
string Results = "";
foreach (tessnet2.Word word in result)
{
Results += word.Confidence + ", " + word.Text + ", " + word.Left + ", " + word.Top + ", " + word.Bottom + ", " + word.Right + "\n";
}
Some online API's work pretty well: ocr.space and Google Cloud Vision. Both of these are free, as long as you do less than 1000 OCR's per month. You can drag & drop an image to do a quick manual test to see how they perform for your images.
I find OCR.space easier to use (no messing around with nuget libraries), but, for my purpose, Google Cloud Vision provided slightly better results than OCR.space.
Google Cloud Vision example:
GoogleCredential cred = GoogleCredential.FromJson(json);
Channel channel = new Channel(ImageAnnotatorClient.DefaultEndpoint.Host, ImageAnnotatorClient.DefaultEndpoint.Port, cred.ToChannelCredentials());
ImageAnnotatorClient client = ImageAnnotatorClient.Create(channel);
Image image = Image.FromStream(stream);
EntityAnnotation googleOcrText = client.DetectText(image).First();
Console.Write(googleOcrText.Description);
OCR.space example:
string uri = $"https://api.ocr.space/parse/imageurl?apikey=helloworld&url={imageUri}";
string responseString = WebUtilities.DoGetRequest(uri);
OcrSpaceResult result = JsonConvert.DeserializeObject<OcrSpaceResult>(responseString);
if ((!result.IsErroredOnProcessing) && !String.IsNullOrEmpty(result.ParsedResults[0].ParsedText))
return result.ParsedResults[0].ParsedText;
A new API is OcrEngine.RecognizeAsync from WinRT/UWP. It can also be used in WinForms:
...
//for AsBuffer
using System.Runtime.InteropServices.WindowsRuntime;
...
async private void button5_Click(object sender, EventArgs e)
{
OcrEngine ocrEngine = null;
ocrEngine = OcrEngine.TryCreateFromUserProfileLanguages();
if (ocrEngine == null) return;
//convert the image to BGRA8 format which is needed by SoftwareBitmap
//is there a better method for this?
Bitmap img = new Bitmap(#"1.png");
byte[] ba = new byte[img.Width * img.Height * 4];
int o = 0;
for (int y = 0; y < img.Height; y++)
{
for (int x = 0; x < img.Width; x++)
{
var p = img.GetPixel(x, y);
ba[o++] = p.B;
ba[o++] = p.G;
ba[o++] = p.R;
ba[o++] = p.A;
}
}
var buffer = ba.AsBuffer();
var outputBitmap = SoftwareBitmap.CreateCopyFromBuffer(
buffer,
BitmapPixelFormat.Bgra8,
img.Width,
img.Height);
var ocrResult = await ocrEngine.RecognizeAsync(outputBitmap);
}
To use WinRT/UWP API in WinForms, add Nuget package "Microsoft.Windows.SDK.Contracts" (version 10.0.17134.100 for Win10 1803 SDK tested here) as described here.