-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a2e2cd1
commit 62e0524
Showing
13 changed files
with
758 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,36 @@ | ||
# WeatherTag | ||
A simple Windows console app for adding weather values to a set of jpg image file EXIF 2.31 Metadata. Using the date and time a photo was taken, the app matches the closest weather reading from a file containing periodic weather readings weatherhistory.csv file. | ||
# Weather Tag | ||
|
||
## Introduction | ||
|
||
This is a simple Windows console (Command Line) application for adding weather values to a set of jpg image file EXIF 2.31 Metadata. Using the date and time a photo was taken, the app matches the closest weather reading from a file containing periodic weather readings weatherhistory.csv file. | ||
|
||
The idea behind this utility is if you can obtain historical weather information for a location near where the photos were taken this information could be saved within the image file metadata. | ||
|
||
Read the blog post: [Capturing the moment and the ambient weather information in photos](https://jmoliver.wordpress.com/2018/07/07/capturing-the-moment-and-the-ambient-weather-information-in-photos/) | ||
|
||
## Dependency with ExifTool | ||
|
||
WeatherTag requires that **ExifTool.exe** be copied into the same folder with WeatherTag.exe on your PC. ExifTool is a utility written by Phil Harvey which can read, write and edit file metadata. Exiftool can be obtained at https://www.sno.phy.queensu.ca/~phil/exiftool/ | ||
|
||
## Installation | ||
|
||
1. On your PC create a folder called "WeatherTag" | ||
2. Copy the WeatherTag.exe file to the folder WeatherTag | ||
3. Copy Exiftool.exe to the Weather Tag folder. | ||
4. Add the Weather Tag folder to the Windows **path** variable. If you are not familiar on how to add a folder to the Windows **path** variable. Search the web for "Add folder to Environment path" for detailed descriptions as it may vary between Windows versions. | ||
|
||
## Usage | ||
There are two pieces of information required for Weather Tag to perform its function: | ||
1. Any number of photos taken with correct Date and Time stamp. | ||
2. A comma separated value file (.csv) in UTF-8 named **weatherhistory.csv** containing periodic weather measurements. The first column values are required to contain a date in MM/DD/YYYY format (Example: 10/5/2018). The second column values are required to contain time values in 12-Hour format (Example: 3:00 PM). The third column should contain ambient temperature values in Celsius. The fourth column values should contain atmospheric humidity in percentage. The fifth column values should contain atmospheric pressure in hectopascals (hPa). The Ambient Temperature, Humidity and Pressure values need not have the measurement units added (°C, %, hPa). | ||
|
||
When WeatherTag.exe is executed within a folder containing image files and the weatherhistory.csv file it will attempt to match the closest weather reading (within an hour) for the date and time a photo was taken. If the **-write** flag is use it will then write the Ambient Temperature, Humidity and Pressure values to the image files' corresponding EXIF metadata fields. | ||
|
||
## Example Files | ||
Within the **example** folder there are 5 sample images along with a historical weather log from a weather station near the location where the photos were taken. Open a Command Prompt within the example folder. Running **WeatherTag.exe** within the folder will match the closest weather measurement contained. Running **WeatherTag.exe -write** will match the weather measurements as well as write the information back to the photo image files. A copy of the original image file will be made with the ***.jpg_original** extension. If you wish to delete the original image files and keep the modified files you can delete them by using the **del *.jpg_original** command. | ||
|
||
## Build WeatherTag.exe | ||
1. Open the **WeatherTag.sln file** in Visual Studio 2017. | ||
2. WeatherTag uses Newtonsoft.JSON Nuget Package which should be downloaded using the Nuget Package Manager. | ||
3. Build Solution <Ctrl>+<Shift>+<B> | ||
4. The **WeatherTag.exe** file should be deposited in the **bin** folder. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio 15 | ||
VisualStudioVersion = 15.0.26730.12 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WeatherTag", "WeatherTag\WeatherTag.csproj", "{0D6E5463-98AA-421B-97CB-7250C6864555}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Release|Any CPU = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{0D6E5463-98AA-421B-97CB-7250C6864555}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{0D6E5463-98AA-421B-97CB-7250C6864555}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{0D6E5463-98AA-421B-97CB-7250C6864555}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{0D6E5463-98AA-421B-97CB-7250C6864555}.Release|Any CPU.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ExtensibilityGlobals) = postSolution | ||
SolutionGuid = {9A25BEFF-2A14-40D7-B25F-7AFC4053C1AD} | ||
EndGlobalSection | ||
EndGlobal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<configuration> | ||
<startup> | ||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> | ||
</startup> | ||
</configuration> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,307 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Linq; | ||
using System.IO; | ||
using Newtonsoft.Json; | ||
|
||
namespace WeatherTag | ||
{ | ||
class Program | ||
{ | ||
|
||
static void Main(string[] args) | ||
{ | ||
|
||
bool WriteToFile = false; | ||
string ActiveFilePath = Directory.GetCurrentDirectory(); | ||
List<WeatherReading> reading = new List<WeatherReading>(); | ||
string[] ImageFiles = Directory.GetFiles(ActiveFilePath, "*.jpg"); | ||
string WeatherHistoryFile = Directory.GetCurrentDirectory() + "\\weatherhistory.csv"; | ||
|
||
//Check for -write flag, if found then matched weather values will be writen back to the jpg file EXIF metadata. | ||
for (int i=0; i<args.Count();i++) | ||
{ | ||
if (args[i].ToString().ToLower().Trim()=="-write") | ||
{ | ||
WriteToFile = true; | ||
} | ||
} | ||
|
||
if (WriteToFile==false) | ||
{ | ||
Console.WriteLine("No changes to file(s) will be performed - To write weather tags use -write flag"); | ||
} | ||
|
||
|
||
Console.WriteLine("Weather file: " + WeatherHistoryFile); | ||
|
||
//Load weather history file into memory | ||
using (var reader = new StreamReader(WeatherHistoryFile)) | ||
{ | ||
while (!reader.EndOfStream) | ||
{ | ||
var line = reader.ReadLine(); | ||
|
||
//Remove any measurement symbols | ||
line = line.Replace("°", ""); | ||
line = line.Replace("C", ""); | ||
line = line.Replace("hPa", ""); | ||
line = line.Replace("%", ""); | ||
|
||
var values = line.Split(','); | ||
|
||
Double ambientTemperature = 99999; | ||
Double humidity = 99999; | ||
Double pressure = 99999; | ||
|
||
DateTime Date1 = DateTime.Parse(values[0].ToString().Trim() + " " + values[1].ToString().Trim()); | ||
|
||
|
||
//Load Ambient Temperature value, if invalid mark as invalid = 99999 | ||
try | ||
{ | ||
ambientTemperature = Double.Parse(values[2].ToString().Trim()); | ||
|
||
//Check for valid Ambient Temperature Range in Celsius | ||
if ((ambientTemperature<-100)||(ambientTemperature>150)) | ||
{ | ||
ambientTemperature = 99999; | ||
} | ||
|
||
} | ||
catch | ||
{ | ||
ambientTemperature = 99999; | ||
} | ||
|
||
//Load Humidity value, if invalid mark as invalid = 99999 | ||
try | ||
{ | ||
humidity = Double.Parse(values[3].ToString().Trim()); | ||
|
||
//Check for valid Humidity Range | ||
if ((humidity < 0) || (humidity > 100)) | ||
{ | ||
humidity = 99999; | ||
} | ||
|
||
} | ||
catch | ||
{ | ||
humidity = 99999; | ||
} | ||
try | ||
{ | ||
pressure = Double.Parse(values[4].ToString().Trim()); | ||
|
||
//Check for valid Pressure Range | ||
if ((pressure < 800) || (pressure > 1100)) | ||
{ | ||
pressure = 99999; | ||
} | ||
} | ||
catch | ||
{ | ||
pressure = 99999; | ||
} | ||
|
||
reading.Add(new WeatherReading(Date1, ambientTemperature, humidity, pressure)); | ||
} | ||
} | ||
|
||
|
||
//For each image file in the folder, add the closest reading from the weather history file. | ||
for (int q = 0; q < ImageFiles.Count(); q++) | ||
{ | ||
|
||
DateTime PhotoDate; | ||
bool NoPhotoDate = false; | ||
|
||
//Get the image's Created Time, if not found or an error occurs set to error value of 1/1/2050 12:00 AM | ||
try | ||
{ | ||
PhotoDate = GetFileDate(ImageFiles[q]); | ||
} | ||
catch | ||
{ | ||
PhotoDate = DateTime.Parse("1/1/2050 12:00 AM"); | ||
NoPhotoDate = true; | ||
} | ||
|
||
Double MinDiffTime = 30; | ||
WeatherReading closestReading = new WeatherReading(DateTime.Parse("1/1/1900 12:00 AM"), 0, 0, 0); | ||
|
||
if (NoPhotoDate == false) | ||
{ | ||
for (int i = 0; i < reading.Count; i++) | ||
{ | ||
TimeSpan DiffTime = PhotoDate - reading[i].ReadingDate; | ||
if (Math.Abs(DiffTime.TotalMinutes) < MinDiffTime) | ||
{ | ||
closestReading = reading[i]; | ||
MinDiffTime = Math.Abs(DiffTime.TotalMinutes); | ||
} | ||
} | ||
} | ||
|
||
|
||
Console.WriteLine("------ File " + (q+1).ToString()+ " of " + ImageFiles.Count() + " ------"); | ||
|
||
if (MinDiffTime < 30) | ||
{ | ||
|
||
string ConsoleOutput = ""; | ||
|
||
if (closestReading.AmbientTemperature != 99999) | ||
{ | ||
ConsoleOutput = ConsoleOutput + " " + closestReading.AmbientTemperature.ToString() + "°C "; | ||
} | ||
else | ||
{ | ||
ConsoleOutput = ConsoleOutput + " -- °C "; | ||
} | ||
|
||
if (closestReading.Humidity != 99999) | ||
{ | ||
ConsoleOutput = ConsoleOutput + " " + closestReading.Humidity.ToString() + " %"; | ||
} | ||
else | ||
{ | ||
ConsoleOutput = ConsoleOutput + " -- % "; | ||
} | ||
|
||
if (closestReading.Pressure != 99999) | ||
{ | ||
ConsoleOutput = ConsoleOutput + " " + closestReading.Pressure.ToString() + " hPa"; | ||
} | ||
else | ||
{ | ||
ConsoleOutput = ConsoleOutput + " -- hPa "; | ||
} | ||
|
||
|
||
Console.WriteLine(ImageFiles[q].ToString().Replace(Directory.GetCurrentDirectory(),"").Trim()+" - "+ConsoleOutput); | ||
if (WriteToFile == true) | ||
{ | ||
string WriteStatus = WriteFileInfo(ImageFiles[q], closestReading); | ||
Console.WriteLine(WriteStatus); | ||
} | ||
|
||
} | ||
else | ||
{ | ||
if (NoPhotoDate == true) | ||
{ | ||
Console.WriteLine(ImageFiles[q].ToString().Replace(Directory.GetCurrentDirectory(), "").Trim() + " - Photo file has no date and time."); | ||
} | ||
else | ||
{ | ||
Console.WriteLine(ImageFiles[q].ToString().Replace(Directory.GetCurrentDirectory(), "").Trim() + " - No reading found."); | ||
} | ||
} | ||
} | ||
|
||
Console.WriteLine(); | ||
|
||
} | ||
|
||
public static DateTime GetFileDate(string file) | ||
{ | ||
//Retrieve Image Date | ||
|
||
List<ExifToolJSON> ExifToolResponse; | ||
string CreateDateTime = ""; | ||
string CreateDate = ""; | ||
string CreateTime = ""; | ||
|
||
// Start Process | ||
Process p = new Process(); | ||
|
||
// Redirect the output stream of the child process. | ||
p.StartInfo.UseShellExecute = false; | ||
p.StartInfo.RedirectStandardOutput = true; | ||
p.StartInfo.CreateNoWindow = true; | ||
p.StartInfo.Arguments = "\"" + file + "\" -CreateDate -mwg -json"; | ||
p.StartInfo.FileName = "exiftool.exe"; | ||
p.Start(); | ||
|
||
|
||
// Read the output stream | ||
string json = p.StandardOutput.ReadToEnd(); | ||
p.WaitForExit(); | ||
|
||
if (json != "") | ||
{ | ||
ExifToolResponse = JsonConvert.DeserializeObject<List<ExifToolJSON>>(json); | ||
CreateDateTime = ExifToolResponse[0].CreateDate.ToString().Trim(); | ||
string[] words = CreateDateTime.Split(' '); | ||
CreateDate = words[0].ToString().Replace(":","/"); | ||
CreateTime = words[1].ToString(); | ||
CreateDateTime = CreateDate + " " + CreateTime; | ||
} | ||
|
||
return DateTime.Parse(CreateDateTime); | ||
} | ||
|
||
public static string WriteFileInfo(string File, WeatherReading reading) | ||
{ | ||
//Write Weather Values back to file | ||
|
||
string output = ""; | ||
// Start the child process. | ||
Process p = new Process(); | ||
|
||
string Arguments = ""; | ||
|
||
if (reading.AmbientTemperature != 99999) | ||
{ | ||
Arguments = Arguments + " -\"AmbientTemperature=" + reading.AmbientTemperature + "\""; | ||
} | ||
if (reading.Humidity != 99999) | ||
{ | ||
Arguments = Arguments + " -\"Humidity=" + reading.Humidity + "\""; | ||
} | ||
if (reading.Pressure != 99999) | ||
{ | ||
Arguments = Arguments + " -\"Pressure=" + reading.Pressure + "\""; | ||
} | ||
|
||
// Redirect the output stream of the child process. | ||
p.StartInfo.UseShellExecute = false; | ||
p.StartInfo.RedirectStandardOutput = true; | ||
p.StartInfo.CreateNoWindow = true; | ||
p.StartInfo.Arguments = "\""+File+"\"" + Arguments; | ||
p.StartInfo.FileName = "exiftool.exe"; | ||
p.Start(); | ||
output = File + Arguments+" --- " + p.StandardOutput.ReadToEnd(); | ||
p.WaitForExit(); | ||
|
||
return output; | ||
} | ||
|
||
public class ExifToolJSON | ||
{ | ||
public string SourceFile { get; set; } | ||
public string CreateDate { get; set; } | ||
} | ||
|
||
|
||
public class WeatherReading | ||
{ | ||
public WeatherReading(DateTime readingDate, double ambientTemperature, double humidity, double pressure) | ||
{ | ||
this.ReadingDate = readingDate; | ||
this.AmbientTemperature = ambientTemperature; | ||
this.Humidity = humidity; | ||
this.Pressure = pressure; | ||
} | ||
|
||
public DateTime ReadingDate { get; set; } | ||
public double AmbientTemperature { get; set; } | ||
public double Humidity { get; set; } | ||
public double Pressure { get; set; } | ||
} | ||
} | ||
} | ||
|
Oops, something went wrong.