// Load all the jpg images found in the folder Boolean LoadDByScanningImageFolder() { DateTimeHandler dateTimeHandler = new DateTimeHandler(); FileInfo fileInfo; ProgressState progressState = new ProgressState(); ImageProperties imgprop; // Collect the image properties for for the 2nd pass... List<ImageProperties> imgprop_list = new List<ImageProperties>(); Dictionary<String, String> dataline = new Dictionary<String, String>(); // Populate the data for the image Dictionary<String, String> markerline = new Dictionary<String, String>(); // Populate the markers database, where each key column corresponds to each key counter in the datatable int index = 0; string datalabel = ""; bool ambiguous_daymonth_order = false; this.imageFilePaths = new DirectoryInfo(this.dbData.FolderPath).GetFiles("*.jpg"); int count = imageFilePaths.Length; if (count == 0) return false; // Create the database and its table before we can load any data into it // Open a connection to the template DB bool result = this.dbData.CreateDB(template); // We generate the data user interface controls from the template description after the database has been created from the template myControls.GenerateControls(dbData); MenuItemControlsInSeparateWindow_Click(this.MenuItemControlsInSeparateWindow, null); //this.ControlsInMainWindow(); this.dbData.CreateTables(); this.dbData.CreateLookupTables(); // We want to show previews of the frames to the user as they are individually loaded // Because WPF uses a scene graph, we have to do this by a background worker, as this forces the update var bgw = new BackgroundWorker() { WorkerReportsProgress = true }; bgw.DoWork += (ow, ea) => { // this runs on the background thread; its written as an anonymous delegate //We need to invoke this to allow updates on the UI this.Dispatcher.Invoke(new Action(() => {; // First, change the UI this.helpControl.Visibility = System.Windows.Visibility.Collapsed; Feedback(null, 0, "Examining images..."); })); // First pass: Examine images to extract its basic properties BitmapSource bmap; BitmapSource corruptedbmp = BitmapFrame.Create(new Uri("pack://application:,,/Resources/corrupted.jpg"));; for (int i = 0; i < count; i++) { fileInfo = imageFilePaths[i]; bmap = null; imgprop = new ImageProperties(); imgprop.Name = fileInfo.Name; imgprop.Folder = Utilities.GetFolderNameFromFolderPath(this.FolderPath); try { // Create the bitmap and determine its ImageQuality bmap = BitmapFrame.Create(new Uri(fileInfo.FullName), BitmapCreateOptions.None, BitmapCacheOption.None); bool dark = PixelBitmap.IsDark(bmap, this.darkPixelThreshold, this.darkPixelRatioThreshold); // imgprop.ImageQuality = (dark) ? (int)Constants.ImageQualityFilters.Dark : (int)Constants.ImageQualityFilters.Ok; } catch { bmap = corruptedbmp; imgprop.ImageQuality = (int) Constants.ImageQualityFilters.Corrupted; } // Get the data from the metadata BitmapMetadata meta = (BitmapMetadata) bmap.Metadata; imgprop.DateMetadata = meta.DateTaken; // For some reason, different versions of Windows treat creation time and modification time differently, // giving inconsisten values. So I just check both and take the lesser of the two. DateTime time1 = File.GetCreationTime(fileInfo.FullName); DateTime time2 = File.GetLastWriteTime(fileInfo.FullName); imgprop.DateFileCreation = (DateTime.Compare (time1, time2) < 0) ? time1 : time2; //string time3 = (meta.DateTaken == null) ? "null" : meta.DateTaken.ToString(); //Debug.Print(fileInfo.Name + " " + time1.ToString() + " " + time2.ToString() + " " + time3); imgprop.ID = index + 1; // its plus 1 as the Database IDs start at 1 rather than 0 imgprop_list.Add (imgprop); index++; int progress = Convert.ToInt32(Convert.ToDouble(index) / Convert.ToDouble(count) * 100); if (index == 1 || (index % 1 == 0) ) { progressState.Message = String.Format ("{0}/{1}: Examining {2}", i, count, imgprop.Name); progressState.Bmap = bmap; bgw.ReportProgress(progress, progressState); } else { progressState.Bmap = null; } } // Second pass: Determine dates ... This can be pretty quick, so we don't really need to give any feedback on it. progressState.Message = "Second pass"; progressState.Bmap = null; bgw.ReportProgress(0, progressState); ambiguous_daymonth_order = DateTimeHandler.VerifyAndUpdateDates(imgprop_list); // Third pass: Update database // TODO This is pretty slow... a good place to make it more efficient by adding multiple values in one shot // We need to get a list of which columns are counters vs notes or fixed coices, // as we will shortly have to initialize them to some defaults List<string> CounterList = new List<string>(); List<string> Notes_and_FixedChoicesList = new List<string>(); List<string> FlagsList = new List<string>(); for (int i = 0; i < this.dbData.dataTable.Columns.Count; i++) { datalabel = this.dbData.dataTable.Columns[i].ColumnName; string type = (string)this.dbData.TypeFromKey[datalabel]; if (null == type) continue; // Column must be the ID, which we skip over as its not a key. if (type.Equals(Constants.COUNTER)) CounterList.Add(datalabel); else if (type.Equals(Constants.NOTE) || type.Equals(Constants.FIXEDCHOICE)) Notes_and_FixedChoicesList.Add(datalabel); else if (type.Equals(Constants.FLAG)) FlagsList.Add(datalabel); } // Create a dataline from the image properties, add it to a list of data lines, // then do a multiple insert of the list of datalines to the database List <Dictionary<string, string>> dataline_list ; //= new List <Dictionary<string, string>> (); List<Dictionary<string, string>> markerline_list ; //= new List<Dictionary<string, string>>(); //for (int i = 0; i < imgprop_list.Count; i++) const int interval = 100; for (int j = 0; j < imgprop_list.Count; j++) { // Create a dataline from the image properties, add it to a list of data lines, // then do a multiple insert of the list of datalines to the database dataline_list = new List<Dictionary<string, string>>(); markerline_list = new List<Dictionary<string, string>>(); for (int i = j; ( (i < (j + interval)) && (i < imgprop_list.Count) ); i++) { // THE PROBLEM IS THAT WE ARE NOT ADDING THESE VALUES IN THE SAME ORDER AS THE TABLE // THEY MUST BE IN THE SAME ORDER IE, AS IN THE COLUMNS. This case statement just fills up // the dataline in the same order as the template table. // It assumes that the key is always the first column dataline = new Dictionary<string, string>(); markerline = new Dictionary<string, string>(); //dataline.Add(Constants.ID, "NULL"); // Add the ID. Its Null to force autoincrement // markerline.Add(Constants.ID, (i+1).ToString()); for (int col = 0; col < dbData.dataTable.Columns.Count; col++) // Fill up each column in order { string col_datalabel = dbData.dataTable.Columns[col].ColumnName; string type = (string) dbData.TypeFromKey [col_datalabel]; if (null == type) continue; // a null will be returned from the ID, as we don't add it to the typefromkey hash. switch (type) { case Constants.FILE: // Add The File name datalabel = (string)this.dbData.DataLabelFromType[Constants.FILE]; dataline.Add(datalabel, imgprop_list[i].Name); break; case Constants.FOLDER: // Add The Folder name datalabel = (string)this.dbData.DataLabelFromType[Constants.FOLDER]; dataline.Add(datalabel, imgprop_list[i].Folder); break; case Constants.DATE: // Add the date datalabel = (string)this.dbData.DataLabelFromType[Constants.DATE]; dataline.Add(datalabel, imgprop_list[i].FinalDate); break; case Constants.TIME: // Add the time datalabel = (string)this.dbData.DataLabelFromType[Constants.TIME]; dataline.Add(datalabel, imgprop_list[i].FinalTime); break; case Constants.IMAGEQUALITY: // Add the Image Quality datalabel = (string)this.dbData.DataLabelFromType[Constants.IMAGEQUALITY]; string str = Constants.IMAGEQUALITY_OK; if (imgprop_list[i].ImageQuality == (int)Constants.ImageQualityFilters.Dark) str = Constants.IMAGEQUALITY_DARK; else if (imgprop_list[i].ImageQuality == (int)Constants.ImageQualityFilters.Corrupted) str = Constants.IMAGEQUALITY_CORRUPTED; dataline.Add(datalabel, str); break; case Constants.DELETEFLAG: // Add the Delete flag datalabel = (string)this.dbData.DataLabelFromType[Constants.DELETEFLAG]; dataline.Add(datalabel, this.dbData.TemplateGetDefault(datalabel)); // Default as specified in the template file, which should be "false" break; case Constants.NOTE: // Find and then Add the Note or Fixed Choice case Constants.FIXEDCHOICE: // Now initialize notes, counters, and fixed choices to the defaults foreach (string tkey in Notes_and_FixedChoicesList) { if (col_datalabel.Equals (tkey)) dataline.Add(tkey, this.dbData.TemplateGetDefault(tkey) ); // Default as specified in the template file } break; case Constants.FLAG: // Now initialize flags to the defaults foreach (string tkey in FlagsList) { if (col_datalabel.Equals(tkey)) dataline.Add(tkey, this.dbData.TemplateGetDefault(tkey)); // Default as specified in the template file } break; case Constants.COUNTER: foreach (string tkey in CounterList) { if (col_datalabel.Equals(tkey)) { dataline.Add(tkey, this.dbData.TemplateGetDefault(tkey)); // Default as specified in the template file markerline.Add(tkey, ""); // TODO ASSUMES THAT MARKER LIST IS IN SAME ORDER AS COUNTERS. THIS MAY NOT BE CORRECT ONCE WE SWITCH ROWS, SO SHOULD DO THIS SEPARATELY } } break; default: Debug.Print("Shouldn't ever reach here!"); break; } } dataline_list.Add(dataline); if (markerline.Count > 0) markerline_list.Add(markerline); index = i; } this.dbData.InsertMultipleRows(Constants.TABLEDATA, dataline_list); this.dbData.InsertMultipleRows(Constants.TABLEMARKERS, markerline_list); j = j + interval - 1; // Get the bitmap again to show it if (imgprop_list[index].ImageQuality == (int)Constants.ImageQualityFilters.Corrupted) bmap = corruptedbmp; else bmap = BitmapFrame.Create(new Uri(System.IO.Path.Combine(this.dbData.FolderPath, imgprop_list[index].Name)), BitmapCreateOptions.None, BitmapCacheOption.None); // Show progress. Since its slow, we may as well do it every update int progress2 = Convert.ToInt32(Convert.ToDouble(index) / Convert.ToDouble(count) * 100); progressState.Message = String.Format("{0}/{1}: Adding {2}", index, count, imgprop_list[index].Name); progressState.Bmap = bmap; bgw.ReportProgress(progress2, progressState); } // this.dbData.AddNewRow(dataline); // this.dbData.AddNewRow(markerline, Constants.TABLEMARKERS); }; bgw.ProgressChanged += (o, ea) => { // this gets called on the UI thread ProgressState progstate = (ProgressState)ea.UserState; Feedback (progressState.Bmap, ea.ProgressPercentage, progressState.Message); this.feedbackCtl.Visibility = System.Windows.Visibility.Visible; }; bgw.RunWorkerCompleted += (o, ea) => { // this.dbData.GetImagesAll(); // Now load up the data table // Get rid of the feedback panel, and show the main interface this.feedbackCtl.Visibility = Visibility.Collapsed; this.feedbackCtl.ShowImage = null; this.markableCanvas.Visibility = Visibility.Visible; // Finally warn the user if there are any ambiguous dates in terms of day/month or month/day order if (ambiguous_daymonth_order) { DlgMessageBox dlgMB = new DlgMessageBox (); dlgMB.MessageTitle = "Timelapse was unsure about the month / day order of your image's dates"; dlgMB.MessageProblem = "Timelapse is extracting the dates from your images. However, it cannot tell if the dates are in day/month order, or month/day order."; dlgMB.MessageReason = "Image date formats can be ambiguous. For example, is 2015/03/05 March 5 or May 3?"; dlgMB.MessageSolution = "If Timelapse gets it wrong, you can correct the dates by choosing" + Environment.NewLine; dlgMB.MessageSolution += "\u2022 Edit Menu -> Dates -> Swap Day and Month."; dlgMB.MessageHint = "If you are unsure about the correct date, try the following." + Environment.NewLine; dlgMB.MessageHint += "\u2022 If your camera prints the date on the image, check that." + Environment.NewLine; dlgMB.MessageHint += "\u2022 Look at the images to see what season it is (e.g., winter vs. summer)." + Environment.NewLine; dlgMB.MessageHint += "\u2022 Examine the creation date of the image file." + Environment.NewLine; dlgMB.MessageHint += "\u2022 Check your own records."; dlgMB.ButtonType = MessageBoxButton.OK; dlgMB.IconType = MessageBoxImage.Information; dlgMB.ShowDialog(); } LoadComplete(true); // If we want to import old data from the ImageData.xml file, we can do it here... // Check to see if there is an ImageData.xml file in here. If there is, ask the user // if we want to load the data from that... if (File.Exists(System.IO.Path.Combine(this.FolderPath, Constants.XMLDATAFILENAME))) { DlgImportImageDataXMLFile dlg = new DlgImportImageDataXMLFile(); dlg.Owner = this; bool? result3 = dlg.ShowDialog(); if (result3 == true) { ImageDataXML.Read(System.IO.Path.Combine(this.FolderPath, Constants.XMLDATAFILENAME), dbData.templateTable, dbData); SetImageFilterAndIndex(this.dbData.State_Row, this.dbData.State_Filter); // to regenerate the controls and markers for this image } } }; bgw.RunWorkerAsync(); return true; }
private void RescanDates() { DateTimeHandler dateTimeHandler = new DateTimeHandler(); FileInfo fileInfo; // Collect the image properties for for the 2nd pass into a list... ImageProperties imgprop; List<ImageProperties> imgprop_list = new List<ImageProperties>(); Dictionary<String, String> dataline = new Dictionary<String, String>(); // Populate the data for the image // This tuple list will hold the id, key and value that we will want to update in the database List<Tuple<int, string, string>> list_to_update_db = new List<Tuple<int, string, string>>(); // This list will hold key / value pairs that will be bound to the datagrid feedback, // which is the way to make those pairs appear in the data grid during background worker progress updates ObservableCollection<MyFeedbackPair> MyFeedbackPairList = new ObservableCollection<MyFeedbackPair>(); this.dgFeedback.ItemsSource = MyFeedbackPairList; // all the different formats used by cameras, including ambiguities in month/day vs day/month orders. DateTimeStyles styles; CultureInfo invariantCulture; invariantCulture = CultureInfo.CreateSpecificCulture(""); styles = DateTimeStyles.None; var bgw = new BackgroundWorker() { WorkerReportsProgress = true }; bgw.DoWork += (ow, ea) => { // this runs on the background thread; its written as an anonymous delegate // We need to invoke this to allow updates on the UI this.Dispatcher.Invoke(new Action(() => { ; // First, change the UIprovide some feedback bgw.ReportProgress(0, new FeedbackMessage("Pass 1: Examining all images...", "Checking if dates/time differ")); })); // Pass 1. Check to see what dates/times need updating. int count = dbData.dataTable.Rows.Count; int j = 1; for (int i = 0; i < count; i++) { fileInfo = new FileInfo(System.IO.Path.Combine(dbData.FolderPath, dbData.dataTable.Rows[i][Constants.FILE].ToString())); BitmapSource bmap = null; imgprop = new ImageProperties(); // We will store the various times here imgprop.Name = dbData.dataTable.Rows[i][Constants.FILE].ToString(); imgprop.ID = Int32.Parse(dbData.dataTable.Rows[i][Constants.ID].ToString()); string message = ""; try { // Get the image (if its there), get the new dates/times, and add it to the list of images to be updated // Note that if the image can't be created, we will just to the catch. bmap = BitmapFrame.Create(new Uri(fileInfo.FullName), BitmapCreateOptions.None, BitmapCacheOption.None); // First we try to see if can get a valid and parsable metadata date and time BitmapMetadata meta = (BitmapMetadata)bmap.Metadata; // Get the data from the metadata if (null != meta.DateTaken) { DateTime dtDate; if (DateTime.TryParse(meta.DateTaken, invariantCulture, styles, out dtDate)) { imgprop.FinalDate = DateTimeHandler.StandardDateString(dtDate); imgprop.FinalTime = DateTimeHandler.StandardTimeString(dtDate); message += " Using metadata timestamp"; } } else // Fallback as no meta data: We have to use the file date { // For some reason, different versions of Windows treat creation time and modification time differently, // giving inconsistent values. So I just check both and take the lesser of the two. DateTime creationTime = File.GetCreationTime(fileInfo.FullName); DateTime writeTime = File.GetLastWriteTime(fileInfo.FullName); DateTime fileTime = (DateTime.Compare(creationTime, writeTime) < 0) ? creationTime : writeTime; imgprop.FinalDate = DateTimeHandler.StandardDateString(fileTime); imgprop.FinalTime = DateTimeHandler.StandardTimeString(fileTime); message += " Using File timestamp"; } if (imgprop.FinalDate.Equals(dbData.dataTable.Rows[i][Constants.DATE].ToString())) { message += ", same date"; imgprop.FinalDate = ""; // If its the same, we won't copy it } else message += ", different date"; if (imgprop.FinalTime.Equals(dbData.dataTable.Rows[i][Constants.TIME].ToString())) { message += ", same time"; imgprop.FinalTime = ""; // If its the same, we won't copy it } else message += ", different time"; imgprop_list.Add(imgprop); } catch // Image isn't there { message += " , skipping as cannot open image."; } j++; bgw.ReportProgress(0, new FeedbackMessage(imgprop.Name, message)); if (i % 100 == 0) System.Threading.Thread.Sleep(25); // Put in a delay every now and then, as otherwise the UI won't update. } // Pass 2. Update each date as needed string msg = ""; bgw.ReportProgress(0, new FeedbackMessage("Pass 2: For selected images", "Updating only when dates or times differ...")); for (int i = 0; i < imgprop_list.Count; i++) { if ( ! (imgprop_list[i].FinalDate.Equals("")) && !(imgprop_list[i].FinalTime.Equals(""))) { // Both date and time need updating list_to_update_db.Add(new Tuple<int, string, string>(imgprop_list[i].ID, Constants.DATE, imgprop_list[i].FinalDate)); list_to_update_db.Add(new Tuple<int, string, string>(imgprop_list[i].ID, Constants.TIME, imgprop_list[i].FinalTime)); msg = "Date / Time updated to: " + imgprop_list[i].FinalDate + " " + imgprop_list[i].FinalTime; } else if ( !(imgprop_list[i].FinalDate.Equals (""))) { // Only date needs updating list_to_update_db.Add(new Tuple<int, string, string>(imgprop_list[i].ID, Constants.DATE, imgprop_list[i].FinalDate)); msg = "Date updated to: " + imgprop_list[i].FinalDate; } else if ( !(imgprop_list[i].FinalTime.Equals (""))) { list_to_update_db.Add(new Tuple<int, string, string>(imgprop_list[i].ID, Constants.TIME, imgprop_list[i].FinalTime)); // dbData.RowSetValueFromID(Constants.TIME, imgprop_list[i].FinalTime, imgprop_list[i].ID); // OLD WAY: ONE ROW AT A TIME. Can DELETE THIS msg = "Time updated to: " + imgprop_list[i].FinalTime; } else { msg = "Updating not required"; } bgw.ReportProgress(0, new FeedbackMessage(imgprop_list[i].Name, msg)); if (i % 100 == 0) System.Threading.Thread.Sleep(25); // Put in a delay every now and then, as otherwise the UI won't update. } bgw.ReportProgress(0, new FeedbackMessage("Writing to database...", "Please wait")); System.Threading.Thread.Sleep(25); dbData.RowsUpdateByRowIdKeyVaue(list_to_update_db); // Write the updates to the database bgw.ReportProgress(0, new FeedbackMessage("Done", "Done")); }; bgw.ProgressChanged += (o, ea) => { FeedbackMessage message = (FeedbackMessage)ea.UserState; MyFeedbackPairList.Add(new MyFeedbackPair { Image = message.ImageName, Message = message.Message }); this.dgFeedback.ScrollIntoView(dgFeedback.Items[dgFeedback.Items.Count - 1]); }; bgw.RunWorkerCompleted += (o, ea) => { this.OkButton.IsEnabled = false; this.CancelButton.IsEnabled = true; }; bgw.RunWorkerAsync(); }