I have reworded my question from the previous one. I have created a Desktop recorder and it works perfectly except one thing. When I try to encode the video and place the media in my output folder C:\Videos it throws an exception.
Keep in mind that the output .xesc will save to the Videos folder on the C drive. However when i try to convert it to .wmv format it throws the following exception.
An unhandled exception of type Microsoft.Expression.Encoder.InvalidMediaFileException occured in Microsoft.Expression.Encoder.dll Aditional Information: Access Denied (Exception from HRESULT: 0x80070005(E_AccessDenied))
I have posted the encoder below.
**** Source Code ******
Here is the source code dealing with the encoding. I was working on a few things on it and if you see any mistakes or anything to better it then let me know. It works perfectly and puts the .xesc formatbut it will not save the .wmv
void Encode(string jobPath)
{
using (Job j = new Job())
{
MediaItem mediaItem = new MediaItem(Environment.GetFolderPath(Environment.SpecialFolder.MyVideos) + #"\IvanSoft Desktop Recorder");
var size = mediaItem.OriginalVideoSize;
WindowsMediaOutputFormat WMV_Format = new WindowsMediaOutputFormat();
WMV_Format.VideoProfile = new Microsoft.Expression.Encoder.Profiles.AdvancedVC1VideoProfile();
WMV_Format.AudioProfile = new Microsoft.Expression.Encoder.Profiles.WmaAudioProfile();
WMV_Format.VideoProfile.AspectRatio = new System.Windows.Size(16, 9);
WMV_Format.VideoProfile.AutoFit = true;
if (size.Width >= 1920 && size.Height >= 1080)
{
WMV_Format.VideoProfile.Size = new System.Drawing.Size(1920, 1080);
WMV_Format.VideoProfile.Bitrate = new Microsoft.Expression.Encoder.Profiles.VariableUnconstrainedBitrate(6000);
}
else if (size.Width >= 1280 && size.Height >= 720)
{
WMV_Format.VideoProfile.Size = new System.Drawing.Size(1280, 720);
WMV_Format.VideoProfile.Bitrate = new Microsoft.Expression.Encoder.Profiles.VariableUnconstrainedBitrate(4000);
}
else
{
WMV_Format.VideoProfile.Size = new System.Drawing.Size(size.Width, size.Height);
WMV_Format.VideoProfile.Bitrate = new Microsoft.Expression.Encoder.Profiles.VariableUnconstrainedBitrate(2000);
}
mediaItem.VideoResizeMode = VideoResizeMode.Letterbox;
mediaItem.OutputFormat = WMV_Format;
j.MediaItems.Add(mediaItem);
j.CreateSubfolder = false;
j.OutputDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyVideos) + #"\IvanSoft Desktop Recorder.xesc";
j.EncodeProgress += new EventHandler<EncodeProgressEventArgs>(j_EncodeProgress);
j.Encode();
}
}
This is not necessary a problem because i can convert the .xesc manually but that takes time. I would like it to work when i press Save_btnClik It will encode like its supposed to. This happens in win8.1 and win10.
What kind of permission do i need to gain access?
Why you dont use "SaveFileDiaLog". I think it is the best method. You can find it in "Toolbox" in Winform_application Visual studio
Ok I found out that Microsoft Expression Encoder 4 does not truely save a file to a .wmv format. It only changes the .xesc to a .wmv.
I found this out by once I got it converted to what I thought was a .wmv I loaded it to Movie Maker and it said that .xesc was not supported.
So the ultimate outcome is that MEE4 encoder will produce a .xesc format. Then I also noticed that third party converters that truely convert the file. The video and sound is not synced. So unless I missed a piece of code somewhere, third party converters are not the way to go.
So I will have to figure a way to truly convert .xesc to another format and retain frame speed and sync.
Related
I have a (hopefully) simple question (I don’t have found an answer, that fit’s by all of my searches).
I work with Xamarin.Forms 1.4.1-Pre-1.
In my app, I have:
byte[] AvatarErfassung; // Bytearray, to later update the webservice
var Avatar = new Image { HeightRequest = 71, WidthRequest = 61, HorizontalOptions = LayoutOptions.Start };
Avatar.Source = "SymbolMann.jpg";
where the image "SymbolMann.jpg” is included as project-resource (in each project) and showed on a page (without problems).
I now want to put the image in a byte-array to send it to our webservice.
I don't have found any way to access the image "SymbolMann.jpg" (to load it in a byte-array) or to use (however) the Avatar.Source therefore.
Question:
How to get the image “SymbolMann.jpg” into the byte-array “AvatarErfassung” ?
Thanks for every answer
Hi Dimitris
Thanks for your (fast) replay.
As I wrote, I work with Xamarin.Forms.
The images are stored as resources:
in \Resources\Drawable\ (for Android), \Resources\ (for iOS) and in the root (for WP).
I have to load the image on a content-page.
If I overtake your code, to the line:
var assembly = this.GetType().GetTypeInfo().Assembly;
I have the error-message (translated to English):
“System.Type don’t contain a definition for GetTypeInfo method (is a using missing?)”
Do I have to add a specific using?
You write:
// you can replace "this.GetType()" with "typeof(MyType)", where MyType is any type in your assembly.
What do I have to place as type?
= typeof(????).GetTypeInfo().Assembly:
Thanks for a further reply.
Update #2:
Firts, thanks for your patience…
Unfortunately in VS2013, I don’t find any function “resolve” that shows me the missing using - but I have find out the missing using my self :-)
In the meantime I have added
using System.Reflection;
Now, the line:
var assembly = this.GetType().GetTypeInfo().Assembly;
is resolved and compiled without error
Further, I have changed the type of the .jpg from “Android Resource” to “Embedded Ressource”.
The App then crashes in the line:
long length = s.Length;
as it seems that s is null (I think the jpg is not found)
Further - if I change the type to “Embedded Ressource” - the image is not found any more by Xamarin.Forms (Avatar.Source = "SymbolMann.jpg";)
If I change the type of the .jpg to “Compile” I can’t compile the app.
Error-message:
A namespace can’t contain directly members like fields or methods.
So… what is the correct type to the resource (so that it can be loaded with assembly.GetManifestResourceStream()?
Do I have to add something to the filename (SymbolMann.jpg) so that it will be found?
How do I have to change Avatar.Source = "SymbolMann.jpg" so that it is found (if I change the type from “Android Resource” to anything else)?
Once again my needs:
On a registration-page, default-images (symbols) are showed as avatar for man and woman in a standard-Xamarin.Forms image (avatar.source = “SymbolMann.jpg” / “SymbolFrau.jpg”).
The .jpg’s are stored in the standard-directories for each project (Android, iOS and WP) where the are accessible without problems from the image-object.
The user then can take one of the default-images or load another one over mediapicker.
If the user the tap the button “Register”, I have to create a byte-array from the Image to send it via json to our webservice.
If the user select another image via mediapicker, I have access to the stream, the problem is, to become a byte-array from the default-images (in every platform).
Once again thanks for your help...
You can easily do this, by reading the resource stream from the assembly:
var assembly = this.GetType().GetTypeInfo().Assembly; // you can replace "this.GetType()" with "typeof(MyType)", where MyType is any type in your assembly.
byte[] buffer;
using (Stream s = assembly.GetManifestResourceStream("SymbolMann.jpg"))
{
long length = s.Length;
buffer = new byte[length];
s.Read(buffer, 0, (int)length);
}
// Do something with buffer
To get the assembly that holds the embedded resources of your project you need to call the GetTypeInfo method on a type included in that assembly. Hence, typeof(X), where "X" is any type in your assembly. For example, if you are using a custom page called MyCustomPage:
public class MyCustomPage : ContentPage {}
... this is what you would pass: typeof(MyCustomPage)
You need any instance of type Type (this is what the typeof keyword returns basically). The alternative is to call the GetType() method on any object that is included in your project.
Note that you will not be able to get the correct assembly if you call typeof(X) on a type that is not included in your project. Eg. calling typeof(ContentPage).GetTypeInfo().Assembly, would return the assembly that the ContentPage type resides in. Which is not the one that includes your resources. I hope this is not confusing. If it is, please let me know.
Now, the GetTypeInfo method is an extension method included in the System.Reflection namespace. When the text editor in Xamarin Studio does not recognize something, it highlights it with red. Right-clicking on that will give you a Resolve option in the context menu:
If the type or method can be resolved, it will show that option.
More on images in Xamarin Forms here.
Thanks #FredWenger and #Dimitris - here's my solution based on both your answers:
Add your image to the Xamarin Forms (Portable) project wherever you like. I just
went for the root: MyProject\DaffyDuck.jpg
Mark the image as an Embedded Resource (file properties -> build
action)
Call the function referencing the Embedded Resource like this:
ImageDataFromResource("MyProject.DaffyDuck.jpg")
Notice that you need to include the project name to get the assembly name *.
This is the function:
public byte[] ImageDataFromResource(string r)
{
// Ensure "this" is an object that is part of your implementation within your Xamarin forms project
var assembly = this.GetType().GetTypeInfo().Assembly;
byte[] buffer = null;
using (System.IO.Stream s = assembly.GetManifestResourceStream(r))
{
if (s != null)
{
long length = s.Length;
buffer = new byte[length];
s.Read(buffer, 0, (int)length);
}
}
return buffer;
}
If you need to reference the loaded data in an ImageView control do
it thus:
ImageView.Source = ImageSource.FromStream(() => new MemoryStream(imageData));
*NB: If step 3 doesn't work and you want to check your embedded resource names call this: var names = assembly.GetManifestResourceNames(); and see what is listed in your project.
First, many thanks to Dimitris who has guided me to the right direction!
Conclusion:
The base:
I work with VS2013 - update 2, Xamarin.Forms (1.4.1-Pre1) and my app is based on the template “Blank App (Xamarin.Forms Shared)” (not PCL) and I use all platforms (Android, iOS, WP).
On a user-registration-page the user can select an image as avatar (that then later is showed to his postings). There are two default-images (one for male one for female), that are showed, depending of the sex of the user. The user then can overtake one of the default-images or select another image from the media-picker.
The default-images were stored on the default-locations for content-images (for Android e.g. under /Resources/Drawable/).
The images were showed in a XF-Standard-Image-Object with Image-Source = “xxx.jpg”.
This has worked without problems.
The problem:
As the user-data (including avatar) are stored on a SQL-Server (over a Json-web-service), I have to convert the image-data to a byte-array (to send it then with the other data as string to the web-service).
The problem was, to load the images from the default-directories and convert it to a byte-array (whereby the file-access was the problem -> not reachable).
The solution:
Thanks Dimitris, that have guided me in the correct direction, I have implemented the following solution:
In all projects (Android, iOS and WP), I have added a new directory “\Embedded\” (directly under the root).
Then, I have copied my two jpg’s (“SymbolMann.jpg” and "SymbolFrau.jpg”) in this directory (for all Platforms).
Then, I have changed the resource-type to the images from e.g. “AndroidResource” to “Embedded Resource” (for all platforms the same type).
In my Startup-Code to the app (app.cs), I have added:
//
string cNameSpace = "";
switch (Device.OS)
{
case TargetPlatform.WinPhone:
cNameSpace = "MatrixGuide.WinPhone";
break;
case TargetPlatform.iOS:
cNameSpace = "MatrixGuide.iOS";
break;
case TargetPlatform.Android:
cNameSpace = "MatrixGuide.Droid";
break;
}
GV.cEmbeddedAblage = cNameSpace + ".Embedded.";
where GV.cEmbeddedAblage is simply a global variable.
To checkout the namespace-names, I had to right-click the projects for each platform and have a look at the settled namespace-name (can varies in your project).
The .Embedded. is the new created directory (same name on all platforms).
Additionally, I have created global byte-arrays for the male-image (GV.ByteSymbolMann) and for the female-image (GV.ByteSymbolFrau).
In the startup-Code of the registration-page, I have added:
string cFilename = "";
if (GV.ByteSymbolMann == null) // not yet loaded
{
cFilename = GV.cEmbeddedAblage + "SymbolMann.jpg";
var assembly = this.GetType().GetTypeInfo().Assembly;
byte[] buffer;
using (Stream s = assembly.GetManifestResourceStream(cFilename))
{
long length = s.Length;
buffer = new byte[length];
s.Read(buffer, 0, (int)length);
GV.ByteSymbolMann = buffer; // Overtake byte-array in global variable for male
}
}
//
if (GV.ByteSymbolFrau == null) // not yet loaded
{
cFilename = GV.cEmbeddedAblage + "SymbolFrau.jpg";
var assembly = this.GetType().GetTypeInfo().Assembly;
byte[] buffer;
using (Stream s = assembly.GetManifestResourceStream(cFilename))
{
long length = s.Length;
buffer = new byte[length];
s.Read(buffer, 0, (int)length);
GV.ByteSymbolFrau = buffer; // Overtake byte-array in global variable for female
}
}
The code to loading the .jpg into the image-object, I had to change from:
//Avatar.Source = "SymbolMann.jpg"; // Load from default directory
to
Avatar.Source = ImageSource.FromStream(() => new MemoryStream(GV.ByteSymbolMann)); // Load (stream) from byte-array
I then takeover the selected byte-array (parallel to showing the image) in a further byte-array (for later upload to the web-service as string)
AvatarErfassung = GV.ByteSymbolMann;
If the user change the selection (e.g. to woman):
Avatar.Source = ImageSource.FromStream(() => new MemoryStream(GV.ByteSymbolFrau));
AvatarErfassung = GV.ByteSymbolFrau;
Note: If the user select another image from mediapicker, I have directly access to the stream, so this was never the problem.
This solution works for all platforms (Android, iOS, WP).
I hope this may help some other users…
And... finally thanks to Dimitris
Adding on to noelicus's answer:
Setup
Add your image to the root folder of the Xamarin.Forms project, e.g. MyProject\pizza.png
In Visual Studio, right-click on the image file and select Build Action -> EmbeddedResource
Code
public byte[] GetImageFromFile(string fileName)
{
var applicationTypeInfo = Application.Current.GetType().GetTypeInfo();
byte[] buffer;
using (var stream = applicationTypeInfo.Assembly.GetManifestResourceStream($"{applicationTypeInfo.Namespace}.fileName"))
{
if (stream != null)
{
long length = stream.Length;
buffer = new byte[length];
stream.Read(buffer, 0, (int)length);
}
}
return buffer;
}
Example
public class MyPage() : ContentPage
{
public MyPage()
{
var imageAsByteArray = GetImageFromFile("pizza.png");
var pizzaImage = new Image();
pizzaImage.Source = ImageSource.FromStream(() => new MemoryStream(imageAsByteArray));
Content = pizzaImage;
}
byte[] GetImageFromFile(string fileName)
{
var applicationTypeInfo = Application.Current.GetType().GetTypeInfo();
byte[] buffer;
using (var stream = applicationTypeInfo.Assembly.GetManifestResourceStream($"{applicationTypeInfo.Namespace}.{fileName}"))
{
if (stream != null)
{
long length = stream.Length;
buffer = new byte[length];
stream.Read(buffer, 0, (int)length);
}
}
return buffer;
}
}
I had a lot of problems getting this to work. My project was already using FFImageLoading elsewhere, and I found this to be the easiest way to load the .png as a stream:
using (var stream = await ImageService.Instance.LoadCompiledResource("resource_image.png").AsPNGStreamAsync())
{
localBitmap = SKBitmap.Decode(stream);
}
Make sure your resource files have the same name in both iOS and Droid projects. They'll probably already have their build action set to 'BundleResource' (iOS) or 'AndroidResource' (Droid).
Sure, you'll have to install the FFImageLoading Nuget, but you'll be glad you did.
I used noelicus given idea and printed all the names from ManifestResourceNames array and I found there my image name as ProjectName.Droid.image_name.jpeg .Then I just changed the method call from assembly.GetManifestResourceStream("image_name.jpg") to assembly.GetManifestResourceStream("ProjectName.Droid.image_name.jpeg").And it worked.
So the correct answer would be :
Store the image in root folder at PCL.
Make it's build action in properties as Embedded resource.
Then print <b><i>var names = assembly.GetManifestResourceNames()</i></b> array which will be a string array and can be printed using
foreach(var data in names){Debug.WriteLine(data);}
There you will see the resource name for image and then use the method
Stream s = assembly.GetManifestResourceStream("resource_name_you_found.format")
At first, I want to say that I had read all topics related to my problem here, on stackoverflow (and of course googled), but those research provided no solution to my problem.
I'm writing app for Windows Phone and I need to play two sounds simultaneously, but this code doesn't work, because there is slight, but noticeable dealy between two sounds, and there must be NO perceptible delay in my project.
Stream s1 = TitleContainer.OpenStream("C.wav");
Stream s2 = TitleContainer.OpenStream("C1.wav");
SoundEffectInstance sci = sc.CreateInstance();
SoundEffectInstance sci1 = sc1.CreateInstance();
sci.Play();
sci1.Play();
I also tried to perform a simple mix of two wav files, but it doesn't work for a reason that I don't know. (ArgumentException - Ensure that the specified stream contains valid PCM mono or stereo wave data. - is thrown, when calling SoundEffect.FromStream(WAVEFile.Mix(s1, s2));
public static Stream Mix(Stream in1,Stream in2)
{
BinaryWriter bw;
bw = new BinaryWriter(new MemoryStream());
byte[] header = new byte[44];
in1.Read(header, 0, 44);
bw.Write(header);
in2.Seek(44, SeekOrigin.Begin);
BinaryReader r1 = new BinaryReader(in1);
BinaryReader r2 = new BinaryReader(in2);
while (in1.Position != in1.Length)
{
bw.Write((short)(r1.ReadInt16() + r2.ReadInt16()));
}
r1.Dispose();
r2.Dispose();
bw.BaseStream.Seek(0, SeekOrigin.Begin);
return bw.BaseStream;
}
Stream s1 = TitleContainer.OpenStream("C.wav");
Stream s2 = TitleContainer.OpenStream("C1.wav");
s3 = SoundEffect.FromStream(WAVEFile.Mix(s1, s2));
So, does anyone know how to play two sounds at the time?
So your first solution SHOULD work. I have another solution that is very similar with a twist that I KNOW works.
static Stream stream1 = TitleContainer.OpenStream("soundeffect.wav");
static SoundEffect sfx = SoundEffect.FromStream(stream1);
static SoundEffectInstance soundEffect = sfx.CreateInstance();
public void playSound(){
FrameworkDispatcher.Update();
soundEffect.Play();
}
The reason your second solution didnt work is because there are very specific file formats that the windows phone can play.
List of supported formats
http://msdn.microsoft.com/en-us/library/windowsphone/develop/ff462087(v=vs.105).aspx
Reference for this code is my blog
http://www.anthonyrussell.info/postpage.php?name=60
Edit
You can see the above solution working here
http://www.windowsphone.com/en-us/store/app/xylophone/fe4e0fed-1130-e011-854c-00237de2db9e
Edit#2
In response to the comment below that this code doesn't work I have also posted a working, published app on my blog that implements this very code. It's called Xylophone, it's free and you can download the code here at the bottom of the page.
http://anthonyrussell.info/postpage.php?name=60
I'm writing a .NET 4 application that imports and saves images for printing. It's important that the saved images resolution (DPI not pixel dimensions) be set to the value we specify so they print correctly.
Some of the images we import come without the resolution value (bad EXIF when they were generated), so we have to correct that before writing them. We use Bitmap.SetResolution for that. It works fine on XP and Windows 8, but when we write (Bitmap.Save) the images on Windows 7, they are always written with the original resolution meta info, ignoring SetResolution.
Here's a test we made, works on XP and 8, not on 7.
string originalFile = #"D:\temp\img\original_img.jpg";
string newFile = #"D:\temp\img\new_img.jpg";
Bitmap bitmap = (Bitmap)Image.FromFile(originalFile);
bitmap.SetResolution(200, 200);
bitmap.Save(newFile, ImageFormat.Jpeg);
Image image = Image.FromFile(newFile);
int dpiX = (int)Math.Round(image.HorizontalResolution, MidpointRounding.ToEven);
int dpiY = (int)Math.Round(image.VerticalResolution, MidpointRounding.ToEven);
Console.WriteLine("DPI is {0} x {1}", dpiX, dpiY);
Before saving, debug always shows the correct resolution assigned by SetResolution, the saved image is where the problem is.
This is probably what was reported here:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/62368caa-05f4-4798-9c59-5d82f881a97c/systemdrawingbitmapsetresolution-is-completely-broken-on-windows-7?forum=netfxbcl
But the issue there seems to remain unsolved. Is there really no way to just make it work? Do I have to use extra libraries for this?
I've found a workaround that will do the job. It's not elegant but...
Instead of applying the resolution to the original image, make a copy of it and work on the copy:
Bitmap bitmap = (Bitmap)Image.FromFile(originalFile);
Bitmap newBitmap = new Bitmap(bitmap)
newBitmap.SetResolution(200, 200);
newBitmap.Save(newFile, ImageFormat.Jpeg);
Now it works on Windows 7. Go figure.
I like Hans Passant's idea, though, it's cleaner. I don't know if what I did messes up with the image, if there is recompression or not.
Hmya, this is a bug in a Windows component. The Windows group is always very reluctant to get bugs like this fixed, breaking changes are postponed to a next Windows version. It did get fixed in Windows 8. Do consider how unusual it is what you are doing, the DPI of an image should always be set by the device that recorded the image. Like the camera or scanner, they never get this wrong. There just isn't any device around that has a 200 dots-per-inch resolution.
If you are desperate enough to find a workaround then you could consider patching the file itself. Not hard to do for a JPEG file, the fields in the file header are pretty easy to get to:
using System.IO;
...
public static void SetJpegResolution(string path, int dpi) {
using (var jpg = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
using (var br = new BinaryReader(jpg)) {
bool ok = br.ReadUInt16() == 0xd8ff; // Check header
ok = ok && br.ReadUInt16() == 0xe0ff;
br.ReadInt16(); // Skip length
ok = ok && br.ReadUInt32() == 0x4649464a; // Should be JFIF
ok = ok && br.ReadByte() == 0;
ok = ok && br.ReadByte() == 0x01; // Major version should be 1
br.ReadByte(); // Skip minor version
byte density = br.ReadByte();
ok = ok && (density == 1 || density == 2);
if (!ok) throw new Exception("Not a valid JPEG file");
if (density == 2) dpi = (int)Math.Round(dpi / 2.56);
var bigendian = BitConverter.GetBytes((short)dpi);
Array.Reverse(bigendian);
jpg.Write(bigendian, 0, 2);
jpg.Write(bigendian, 0, 2);
}
}
I'm attempting to create a 2D array of integers that load in data from a .txt file, however when it compiles this line of code: string line = stream.ReadLine(); it gives me the error in the thread title and also this:
Attempt to access the method failed, System.IO.StreamReader..ctor(System.String)
I understand there may be differences between using txt files between PC and WP7/360 however I am sure that it's possible because an app my College tutor made used .txt files on his WP7 handset, what sort of changes are there that I must make for Windows Phone to correctly read the file?
For those wanting extra informtion here is the entire method for loading the txt to the int array:
public void loadMap(string mapFileName)
{
int x = 0, y = 0;
StreamReader stream = new StreamReader(mapFileName);
do
{
string line = stream.ReadLine();
string[] numbers = line.Split(',');
foreach (string e in numbers)
{
int tile = int.Parse(e);
this.tileID[x, y] = tile;
x++;
}
y++;
}
while (!stream.EndOfStream);
xSize = x;
ySize = y;
stream.Close();
}
EDIT
Makings some progress now although I've hit a new error by using IsolatedStorage methods,
I've added this code before the parsing code:
var store = IsolatedStorageFile.GetUserStoreForApplication();
var readStream = new IsolatedStorageFileStream(mapFileName, FileMode.Open, store);
var stream = new StreamReader(readStream);
And now here: var readStream = new IsolatedStorageFileStream(mapFileName, FileMode.Open, store); I get the error:
Operation not permitted on IsolatedStorageFileStream.
Any Ideas?
For security reasons, applications on Windows Phone are sandboxed, which essentially just means that you're prevented from touching things that don't belong to you. Consider using XNA's StorageContainer class instead, which is designed to work around the security limitations inherent to non-PC platforms.
UPDATE
On further investigation, it looks like Windows Phone doesn't provide this functionality. Instead, you should use the System.IO.IsolatedStorage class.
In windows XP "FileInfo.LastWriteTime" will return the date a picture is taken - regardless of how many times the file is moved around in the filesystem.
In Vista it instead returns the date that the picture is copied from the camera.
How can I find out when a picture is taken in Vista? In windows explorer this field is referred to as "Date Taken".
Here's as fast and clean as you can get it. By using FileStream, you can tell GDI+ not to load the whole image for verification. It runs over 10 × as fast on my machine.
//we init this once so that if the function is repeatedly called
//it isn't stressing the garbage man
private static Regex r = new Regex(":");
//retrieves the datetime WITHOUT loading the whole image
public static DateTime GetDateTakenFromImage(string path)
{
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
using (Image myImage = Image.FromStream(fs, false, false))
{
PropertyItem propItem = myImage.GetPropertyItem(36867);
string dateTaken = r.Replace(Encoding.UTF8.GetString(propItem.Value), "-", 2);
return DateTime.Parse(dateTaken);
}
}
And yes, the correct id is 36867, not 306.
The other Open Source projects below should take note of this. It is a huge performance hit when processing thousands of files.
I maintained a simple open-source library since 2002 for extracting metadata from image/video files.
// Read all metadata from the image
var directories = ImageMetadataReader.ReadMetadata(stream);
// Find the so-called Exif "SubIFD" (which may be null)
var subIfdDirectory = directories.OfType<ExifSubIfdDirectory>().FirstOrDefault();
// Read the DateTime tag value
var dateTime = subIfdDirectory?.GetDateTime(ExifDirectoryBase.TagDateTimeOriginal);
In my benchmarks, this code runs over 12 times faster than Image.GetPropertyItem, and around 17 times faster than the WPF JpegBitmapDecoder/BitmapMetadata API.
There's a tonne of extra information available from the library such as camera settings (F-stop, ISO, shutter speed, flash mode, focal length, ...), image properties (dimensions, pixel configurations) and other things such as GPS positions, keywords, copyright info, etc.
If you're only interested in the metadata, then using this library is very fast as it doesn't decode the image (i.e. bitmap). You can scan thousands of images in a few seconds if you have fast enough storage.
ImageMetadataReader understands many files types (JPEG, PNG, GIF, BMP, TIFF, PCX, WebP, ICO, ...). If you know that you're dealing with JPEG, and you only want Exif data, then you can make the process even faster with the following:
var directories = JpegMetadataReader.ReadMetadata(stream, new[] { new ExifReader() });
The metadata-extractor library is available via NuGet and the code's on GitHub. Thanks to all the amazing contributors who have improved the library and submitted test images over the years.
Image myImage = Image.FromFile(#"C:\temp\IMG_0325.JPG");
PropertyItem propItem = myImage.GetPropertyItem(306);
DateTime dtaken;
//Convert date taken metadata to a DateTime object
string sdate = Encoding.UTF8.GetString(propItem.Value).Trim();
string secondhalf = sdate.Substring(sdate.IndexOf(" "), (sdate.Length - sdate.IndexOf(" ")));
string firsthalf = sdate.Substring(0, 10);
firsthalf = firsthalf.Replace(":", "-");
sdate = firsthalf + secondhalf;
dtaken = DateTime.Parse(sdate);
With WPF and C# you can get the Date Taken property using the BitmapMetadata class:
MSDN - BitmapMetada
WPF and BitmapMetadata
In windows XP "FileInfo.LastWriteTime"
will return the date a picture is
taken - regardless of how many times
the file is moved around in the
filesystem.
I have great doubts XP was actually doing that. More likely the tool you used to copy the image from the camera to you hard disk was reseting the File Modified Date to the image's Date Taken.
you'll have to check the EXIF information from the picture. I don't think with regular .Net functions you'll know when the picture was taken.
It might get a little complicated...
There will be EXIF data embedded in the image. There are a ton of examples on the web if you search for EXIF and C#.
//retrieves the datetime WITHOUT loading the whole image
public static DateTime GetDateTakenFromImage(string path)
{
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
using (Image myImage = Image.FromStream(fs, false, false))
{
PropertyItem propItem = null;
try
{
propItem = myImage.GetPropertyItem(36867);
}
catch { }
if (propItem != null)
{
string dateTaken = r.Replace(Encoding.UTF8.GetString(propItem.Value), "-", 2);
return DateTime.Parse(dateTaken);
}
else
return new FileInfo(path).LastWriteTime;
}
}