Im trying to use ImageMagick in a C# application on OSX.
My constraint is that I'm not allowed to install IM on the computer and I can't use homebrew or Magick.Net.
The application downloads ImageMagick-x86_64-apple-darwin20.1.0.tar.gz from https://imagemagick.org/script/download.php and unpacks this to ~/test/
I'm using CliWrap:
var home = "~/test/ImageMagick-7.0.10/";
var result = await Cli
.Wrap(home + "magick")
.WithArguments(new[] { "-quiet", inputPath, outputPath }, true)
.WhenOSX(builder =>
builder.WithEnvironmentVariables(env =>
env
.Set("MAGICK_HOME", home)
.Set("DYLD_LIBRARY_PATH", home + "lib/"
.Set("PATH", home + "bin/"))
)
.WithValidation(CommandResultValidation.None)
.ExecuteBufferedAsync(cts.Token);
When running this I'm getting:
dyld[97894]: Library not loaded: /ImageMagick-7.0.10/lib/libMagickCore-7.Q16HDRI.8.dylib
Referenced from: /Users/niels/test/ImageMagick-7.0.10/bin/magick
Reason: tried: '/ImageMagick-7.0.10/lib/libMagickCore-7.Q16HDRI.8.dylib' (no such file), '/usr/local/lib/libMagickCore-7.Q16HDRI.8.dylib' (no such file), '/usr/lib/libMagickCore-7.Q16HDRI.8.dylib' (no such file)
What can I do?
Related
I have an asp.net core API that was recently updated from .net5 to .net6.
There is a piece of code that should read a duration of an audio file. The code that seems to have worked on previous versions was this:
try
{
//
// NAudio -- Windows only
//
using var fileReader = new AudioFileReader(filePath);
return Convert.ToInt32(Math.Ceiling(fileReader.TotalTime.TotalSeconds));
}
catch (DllNotFoundException)
{
try
{
//
// LibVLCSharp is crossplatform
//
using var libVLC = new LibVLC();
using var media = new Media(libVLC, filePath, FromType.FromPath);
MediaParsedStatus parsed = Task.Run(async () => await media.Parse(MediaParseOptions.ParseNetwork, timeout: 2000).ConfigureAwait(false)).Result;
if (parsed != MediaParsedStatus.Done) throw new ArgumentException("Could not read audio file");
if (!media.Tracks.Any(t => t.TrackType == TrackType.Audio) || (media.Duration <= 100)) throw new ArgumentException("Could not read audio from file");
return Convert.ToInt32(Math.Ceiling(TimeSpan.FromMilliseconds(media.Duration).TotalSeconds));
}
catch (Exception ex) when (ex is DllNotFoundException || ex is LibVLCSharp.Shared.VLCException)
{
try
{
using var fileReader = new Mp3FileReader(filePath);
return Convert.ToInt32(Math.Ceiling(fileReader.TotalTime.TotalSeconds));
}
catch (InvalidOperationException)
{
throw new ArgumentException("Could not read audio file");
}
}
}
The application was deployed on Linux and, I don't know which part of the code did the exact calculation (I am assuming the VLC part), but since the update to .NET6, all of these fail, and since the last fallback is NAudio, we get the following exception:
Unable to load shared library 'Msacm32.dll' or one of its dependencies.
I am using Windows, but I tried running the app with WSL, and I can't get the VLC part to run either - it always throws the following exception (even after installing vlc and vlc dev SDK):
LibVLC could not be created. Make sure that you have done the following:
Installed latest LibVLC from nuget for your target platform.
Unable to load shared library 'libX11' or one of its dependencies. In order to help diagnose loading problems, consider setting the LD_DEBUG environment variable: liblibX11: cannot open shared object file: No such file or directory at LibVLCSharp.Shared.Core.Native.XInitThreads()
at LibVLCSharp.Shared.Core.InitializeDesktop(String libvlcDirectoryPath)
at LibVLCSharp.Shared.Helpers.MarshalUtils.CreateWithOptions(String[] options, Func`3 create)
Is there any clean way to read a duration of an audio file on all platforms?
Needless to say, NAudio works like a charm on Windows, and so does the VLC (with the proper nuget package).
If you install ffmpeg, you can do this quite easily. ffmpeg comes installed in most linux distros by default, but in case it isn't, you can install it with your favorite package manager.
sudo apt install ffmpeg
To install it in windows, you'll need to download the build files, extract it, and add it to the PATH.
Next, install Xabe.FFMpeg package in your project.
Finally, you can call the static method Xabe.FFMpeg.FFMpeg.GetMediaInfo() to get all information regarding your audio file. Here is a sample snippet that I tested on my linux machine.
using System;
using System.IO;
using Xabe.FFmpeg;
namespace Program;
public static class Program
{
public static void Main(string[] args)
{
string filename;
if (args.Length == 0)
{
Console.WriteLine("No arguments found! Provide the audio file path as argument!");
return;
}
else if (File.Exists(filename = args[0]) == false)
{
Console.WriteLine("Given file does not exist!");
return;
}
try
{
var info = FFmpeg.GetMediaInfo(filename).Result;
TimeSpan duration = info.Duration;
Console.WriteLine($"Audio file duration is {duration}");
}
catch(Exception ex)
{
Console.WriteLine(ex);
}
}
}
The error you are seeing is because we were assuming that you would display a video on linux, using X11, so we are always initializing X11. See here.
We shouldn't do that for your use case(because you may not have a GUI available). Please report the issue here : https://code.videolan.org/videolan/LibVLCSharp/-/issues
or even better, submit a pull request on github or gitlab.
As for your question of why did it work on .net 5 and not anymore, I'm not sure we have enough info to tell why, because you didn't send us the error message from that machine.
I would encourage you to take a look at atldotnet. It is a small, well maintained completely managed code / cross platform library without any external dependencies and was accurate detecting audio file duration in all of my test cases (more accurate than ffmpeg). Most common audio formats are supported.
var t = new Track(audioFilePath);
// Works the same way on any supported format (MP3, FLAC, WMA, SPC...)
System.Console.WriteLine("Duration (ms) : " + t.DurationMs);
I have downloaded and attached the FixedVersionRuntime.88.0.705.81.x64 for WebView2 and attached it to my project.
Using the following it should load the necessary page but when loading the WebView is not crashing but no page is loaded:
public async Task InitializeAsync()
{
string installPath = #"C:\Program Files (x86)\WebView2Runtime\Microsoft.WebView2.FixedVersionRuntime.88.0.705.81.x64\";
var webView2Environment = await CoreWebView2Environment.CreateAsync(installPath);
await browserControl.EnsureCoreWebView2Async(webView2Environment);
}
I am then setting the source after this:
await InitializeAsync();
me.Source = new Uri(((MainViewModel)this.DataContext).Config.DefaultURL);
When using the evergreen installer it worked fine but when moving to the fixed version it seems to not load correctly when deployed.
I've tested the following, which seems to work:
Download WebView2 Fixed Version
Example
Given:
WebView2 Fixed Version: Microsoft.WebView2.FixedVersionRuntime.88.0.705.81.x86.cab
Project folder: C:\Projects\WpfTestFixedVersion
Output folder: C:\Projects\WpfTestFixedVersion\WpfTestFixedVersion\bin\Debug
Project compiled using:
Configuration: Debug
Platform: Any CPU (Prefer 32-bit)
Extract files from .cab
Open a cmd window
cmd window
C:\Users\Test\Downloads> expand Microsoft.WebView2.FixedVersionRuntime.88.0.705.81.x86.cab -F:* "C:\Projects\WpfTestFixedVersion\WpfTestFixedVersion\bin\Debug"
Note: When using expand in the above command, the destination folder must already exist and the name must not end with '\'.
C:\Projects\WpfTestFixedVersion\WpfTestFixedVersion\bin\Debug
C:\Projects\WpfTestFixedVersion\WpfTestFixedVersion\bin\Debug\Microsoft.WebView2.FixedVersionRuntime.88.0.705.81.x86
Option 1:
InitializeAsync
public async Task InitializeAsync()
{
string installPath = #".\Microsoft.WebView2.FixedVersionRuntime.88.0.705.81.x86";
var webView2Environment = await CoreWebView2Environment.CreateAsync(installPath);
await browserControl.EnsureCoreWebView2Async(webView2Environment);
}
Option 2:
Note: This option allows one to specify the userDataFolder. If it's not specified, it uses the user's temp folder as the location for the userDataFolder.
InitializeAsync
public async Task InitializeAsync(WebView2 wv, string webCacheDir = "")
{
CoreWebView2EnvironmentOptions options = null;
string tempWebCacheDir = string.Empty;
CoreWebView2Environment webView2Environment = null;
//set value
tempWebCacheDir = webCacheDir;
if (String.IsNullOrEmpty(tempWebCacheDir))
{
//get fully-qualified path to user's temp folder
tempWebCacheDir = System.IO.Path.GetTempPath();
tempWebCacheDir = System.IO.Path.Combine(tempWebCacheDir, System.Guid.NewGuid().ToString("N"));
}
//use with WebView2 FixedVersionRuntime
webView2Environment = await CoreWebView2Environment.CreateAsync(#".\Microsoft.WebView2.FixedVersionRuntime.88.0.705.81.x86", tempWebCacheDir, options);
//webView2Environment = await CoreWebView2Environment.CreateAsync(#"C:\Program Files (x86)\Microsoft\Edge Dev\Application\90.0.810.1", tempWebCacheDir, options);
//webView2Environment = await CoreWebView2Environment.CreateAsync(null, tempWebCacheDir, options);
//wait for CoreWebView2 initialization
await wv.EnsureCoreWebView2Async(webView2Environment);
}
The answer by #user9938 is comprehensive. But please also note that the version of "WebView2Loader.dll" which is in use is very crucial. I had almost the same problem with "Microsoft.WebView2.FixedVersionRuntime.101.0.1210.39.x64" when I tried to use the WebView2 component in the MMC Snap-Ins with types of "HTMLView" or "FormView".
I just copied the abovementioned dll file (version 1.0.1248.0, size=157640 bytes) in a proper path that was accessible for the project (you could just put it beside your project output files first to test it) and then WebView2 browser started to function as expected. Microsoft error messages sometimes (at least in my case) was a little bit misleading and did not convey enough and to the point information.
I received "BadImageFormatException" that normally occurs when you mix platform targets (for example using a dll file compiled in X64 in an application that targeted for x86 or vice versa) or mix native code and .NET but that was not my problem at all. I hope this help one who may stuck in.
So I have a .NET Core 2.2 app running on an Azure VM with Windows Server 2019 which has the following disk configuration:
The disk on the red box is where the App files are located. Now, when Directory.GetCurrentDirectory() is called by the app, it returns the root directory of the project and not the current directory where the app is running. When I tried to run it locally, it works.
To illustrate it:
When run locally, the directory ~/MyApp/bin/Debug/netcoreapp2.2/ is returned by the call. When run in the VM, ~/MyApp/ is returned instead.
Here's the relevant code where the issue happens. What I do here is when the app is closed, it copies the current configuration file from ~/MyApp/bin/Debug/netcoreapp2.2/ to the ~/MyApp/ directory. This code works when run on a local machine, but it is not when running on the VM. How to make sure that calling Directory.GetCurrentDirectory() does indeed point to the directory of the running app?
private void ProcessExit(object sender, EventArgs e)
{
try
{
string rawConfigLocation = _config.GetSection("configurationSettings")["ConfigurationDirectory"];
File.Delete($"{Path.Combine(rawConfigLocation, "Config.json.inc")}");
using (var writer = File.CreateText($"{Path.Combine(Directory.GetCurrentDirectory(), "Config.json.inc")}"))
{
using (var reader = new StreamReader(Path.Combine(Directory.GetCurrentDirectory(), "Config.json")))
{
while (!reader.EndOfStream) writer.WriteLine(reader.ReadLine());
writer.Close();
}
Console.WriteLine(rawConfigLocation);
File.Move($"{Path.Combine(Directory.GetCurrentDirectory(), "Config.json.inc")}", $"{Path.Combine(rawConfigLocation, "Config.json.inc")}");
File.Delete($"{Path.Combine(rawConfigLocation, "Config.json")}");
File.Move($"{Path.Combine(rawConfigLocation, "Config.json.inc")}", $"{Path.Combine(rawConfigLocation, "Config.json")}");
Debug.WriteLine($"New file saved as {Path.Combine(rawConfigLocation, "Config.json")}");
#region [WINDOWS ONLY] Lines for debugging. Will be removed if the issue of incorrect config saving is fixed.
using (EventLog eventLog = new EventLog("MyApp"))
{
eventLog.Source = "MyApp";
eventLog.WriteEntry(
$"Raw Config Location: {_config.GetSection("configurationSettings")["ConfigurationDirectory"]}{Environment.NewLine}" +
$"File that was deleted: {Path.Combine(rawConfigLocation, "Config.json.inc")}{Environment.NewLine}" +
$"Temporary file created: {Path.Combine(Directory.GetCurrentDirectory(), "Config.json.inc")}{Environment.NewLine}" +
$"Current Directory: {Directory.GetCurrentDirectory()}{Environment.NewLine}" +
$"New file saved as {Path.Combine(rawConfigLocation, "Config.json")}",
EventLogEntryType.Information,
101,
1);
}
#endregion
}
}
I have a requirement where I need to run a particular .exe based on the architecture.
My folder structure is like this:
The Tools folder looks like this
Under that the Binaries folder contains 2 sub folder (one each for 32/64 bit)
Each of these folders (x64/x32) looks like below:
The Root folder contains the .bat (start.bat) file that calls the appropriate exe (For example: if it is 32bit, it calls "\Binaries\x86\Tool.exe" or if it is 64bit, it calls "\Binaries\x64\Tool.exe".
The code in start.bat is as below:
#ECHO OFF
if exist "%SYSTEMDRIVE%\Program Files (x86)\" (
start "" /d "%~dp0" "Binaries\x86\Tool.exe"
) else (
start "" /d "%~dp0" "Binaries\x64\Tool.exe"
)
The calling is fine and it calls the particular .exe. The problem comes when the .exe application tries to use the XML file (each folder also contains a XML file, parameters.xml, along with exe) it throws an error. I am accessing the XML file using relative paths like ("./parameters.xml").
I tried recoding the code by using "System.AppDomain.CurrentDomain.BaseDirectory" (as this is a WPF exe). That works for relative path, but another scenario fails. I will explain the same below:
In the application i am getting the instances of SQL installed on the machine. To achieve that I am using the following code:
internal static List < string > SQLServerInstances() {
var sqlInstances = new List < string > ();
try {
using(RegistryKey sqlKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, "").
OpenSubKey(#
"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL")) {
if (sqlKey != null) {
foreach(string versionKeyName in sqlKey.GetValueNames()) {
sqlInstances.Add(versionKeyName);
}
}
}
} catch (Exception de) {
throw de;
}
}
This code returns no instances if I use the batch file to run the .exe. If I run the .exe directly from "Binaries\x64\Tool.exe", this code passes and returns me the SQL instances properly.
I am not sure what the issue is. This might be expected behaviour but seems a bit weird.
Try changing your start.bat code this way:
#ECHO OFF
cd "%~dp0"
if exist "%SYSTEMDRIVE%\Program Files (x86)\" (
start "" "Binaries\x86\Tool.exe"
) else (
start "" "Binaries\x64\Tool.exe"
)
I want to open a file's location and select the file in explorer on Mac, Ubuntu from MonoDevelop.
This code is working on Windows (but it is not working on Mac and Ubuntu):
System.Diagnostics.Process.Start("explorer.exe", "/select, " + fileaddress);
Dim dir_path As String = "/media/os/test"
' Windows path example: dir_path = "C:\test"
Process.Start("file://" & dir_path)
Tested and worked on Ubuntu and Windows XP.
Source: http://www.stevenbrown.ca/blog/archives/156
By 2020-10, in mono 6.10, the above method didn't work on Ubuntu 20.04. The below approach solved the problem.
System.Diagnostics.Process.Start("mimeopen", "/var/tmp");
You can use 'open' on Mac, like this
System.Diagnostics.Process.Start("open", $"-R \"{File_Path_You_Wanna_Select}\"");
Here -R means reveal, to select in the Finder instead of opening.
To find more usage for open, just type open in terminal.
Using Process.Start() you bypass the .NET framework and move into the platform you're running onto, executing an arbitrary process.
On Windows you want to open the Windows Explorer, on Mac you want to open Finder and on Ubuntu it's simply called File Browser.
There is no Environment.OpenFileBrowser(string path) method in the framework, so you will have to let your program determine which platform it is running on, and open the approperiate file viewer.
See How to check the OS version at runtime e.g. windows or linux without using a conditional compilation statement to perform the former.
You are calling an OS specific (Windows) method. That won't work cross-platform.
Try the following inside a function/method:
Example - inside click event:
protected void OnOpen (object sender, EventArgs e)
{
using(FileChooserDialog chooser =
new FileChooserDialog(null,
"Select document to open...",
null,
FileChooserAction.Open,
"Open Selected File",
ResponseType.Accept,
"Discard & Return to Main Page",
ResponseType.Cancel))
{
if (chooser.Run () == (int)ResponseType.Accept)
{
System.IO.StreamReader file = System.IO.File.OpenText (chooser.Filename);
/* Copy the contents to editableTxtView <- This is the Widget Name */
editableTxtView.Buffer.Text = file.ReadToEnd ();
/* If you want to read the file into explorer, thunar, Notepad, etc.,
* you'll have to research that yourself. */
//Close file - - KEEP IT CLEAN - - & deAllocated memory!!
file.Close ();
}
}
}
The file has now been copied into an editable (Default) or read only (set in properties pad) textviewer Gtk widget. From there you should be able to manipulate it as you so choose.