Rob van der Woude's Scripting Pages
Powered by GeSHi

Source code for capturedate.cs

(view source code of capturedate.cs as plain text)

  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Text.RegularExpressions;
  5.  
  6.  
  7. namespace RobvanderWoude
  8. {
  9. 	class CaptureDate
  10. 	{
  11. 		static readonly string progver = "1.03";
  12.  
  13.  
  14. 		#region Global Variables
  15.  
  16. 		static bool confirmsettimestamp = true;
  17. 		static bool readabletimestamp = true;
  18. 		static bool recursive = false;
  19. 		static bool settimestamp = false;
  20. 		static bool wildcards = false;
  21. 		static double timediffthreshold = 3600;
  22. 		static int renamecount = 0;
  23. 		static string searchpattern = "*.*";
  24.  
  25. 		#endregion Global Variables
  26.  
  27.  
  28. 		static int Main( string[] args )
  29. 		{
  30. 			#region Initialize Variables
  31.  
  32. 			string startdir = Environment.CurrentDirectory; // in case no directory is specified
  33. 			string[] filespec;
  34.  
  35. 			#endregion Initialize Variables
  36.  
  37.  
  38. 			#region Parse Command Line
  39.  
  40. 			if ( args.Length == 0 )
  41. 			{
  42. 				return ShowHelp( );
  43. 			}
  44.  
  45. 			foreach ( string arg in args )
  46. 			{
  47. 				if ( arg == "/?" || arg.Length < 2 )
  48. 				{
  49. 					return ShowHelp( );
  50. 				}
  51. 				if ( arg[0] == '/' )
  52. 				{
  53. 					switch ( arg.Substring( 0, 2 ).ToUpper( ) )
  54. 					{
  55. 						case "/D":
  56. 							if ( arg.Length > 3 && arg[2] == ':' )
  57. 							{
  58. 								if ( timediffthreshold != 3600 )
  59. 								{
  60. 									return ShowHelp( "Duplicate switch: /D" );
  61. 								}
  62. 								try
  63. 								{
  64. 									timediffthreshold = Convert.ToDouble( arg.Substring( 3 ) );
  65. 								}
  66. 								catch
  67. 								{
  68. 									return ShowHelp( "Invalid value: \"{0}\"", arg );
  69. 								}
  70. 							}
  71. 							else
  72. 							{
  73. 								return ShowHelp( "Invalid value: \"{0}\"", arg );
  74. 							}
  75. 							break;
  76. 						case "/R":
  77. 							if ( recursive )
  78. 							{
  79. 								return ShowHelp( "Duplicate switch: /R" );
  80. 							}
  81. 							recursive = true;
  82. 							break;
  83. 						case "/S":
  84. 							if ( settimestamp )
  85. 							{
  86. 								return ShowHelp( "Duplicate switch: /S" );
  87. 							}
  88. 							settimestamp = true;
  89. 							break;
  90. 						case "/T":
  91. 							if ( !readabletimestamp )
  92. 							{
  93. 								return ShowHelp( "Duplicate switch: /T" );
  94. 							}
  95. 							readabletimestamp = false;
  96. 							break;
  97. 						case "/Y":
  98. 							if ( !confirmsettimestamp )
  99. 							{
  100. 								return ShowHelp( "Duplicate switch: /Y" );
  101. 							}
  102. 							confirmsettimestamp = false;
  103. 							break;
  104. 						default:
  105. 							return ShowHelp( "Invalid switch: \"{0}\"", arg );
  106. 					}
  107. 				}
  108. 				else
  109. 				{
  110. 					searchpattern = Path.GetFileName( arg );
  111. 					wildcards = ( searchpattern.IndexOfAny( "*?".ToCharArray( ) ) > -1 );
  112. 					startdir = Path.GetDirectoryName( arg );
  113. 					if ( !String.IsNullOrEmpty( startdir ) && !Directory.Exists( startdir ) )
  114. 					{
  115. 						return ShowHelp( "Folder not found: \"{0}\"", startdir );
  116. 					}
  117. 					filespec = Directory.GetFiles( startdir, searchpattern );
  118. 				}
  119. 			}
  120. 			if ( !confirmsettimestamp && !settimestamp )
  121. 			{
  122. 				return ShowHelp( "/Y switch is valid only when combined with /S switch." );
  123. 			}
  124. 			if ( timediffthreshold != 3600 && !settimestamp )
  125. 			{
  126. 				return ShowHelp( "/D switch is valid only when combined with /S switch." );
  127. 			}
  128.  
  129. 			#endregion Parse Command Line
  130.  
  131.  
  132. 			ProcessFolder( startdir );
  133.  
  134. 			return renamecount;
  135. 		}
  136.  
  137.  
  138. 		static void ProcessFolder( string folder )
  139. 		{
  140. 			string[] filespec = Directory.GetFiles( folder, searchpattern );
  141. 			foreach ( string filename in filespec )
  142. 			{
  143. 				ProcessImage( filename );
  144. 			}
  145. 			if ( recursive )
  146. 			{
  147. 				string[] subdirs = Directory.GetDirectories( folder );
  148. 				foreach ( string subdir in subdirs )
  149. 				{
  150. 					ProcessFolder( subdir );
  151. 				}
  152. 			}
  153. 		}
  154.  
  155.  
  156. 		static void ProcessImage( string filename )
  157. 		{
  158. 			#region Read First MB of File
  159.  
  160. 			long filesize = new FileInfo( filename ).Length;
  161. 			int buffersize = Convert.ToInt32( Math.Min( filesize, 1048576 ) ); // Buffer size is 1 MB or file size, whichever is the smallest
  162. 			StreamReader file = new StreamReader( filename );
  163. 			char[] buffer = new Char[buffersize];
  164. 			_ = file.Read( buffer, 0, buffersize - 1 ); // Read only the first 1 MB of the file (or the entire file if smaller than 1 MB)
  165. 			file.Close( );
  166. 			string header = new String( buffer );
  167.  
  168. 			if ( String.IsNullOrEmpty( header ) )
  169. 			{
  170. 				ShowHelp( "Could not open file \"{0}\"", filename );
  171. 				return;
  172. 			}
  173.  
  174. 			#endregion Read First MB of File
  175.  
  176.  
  177. 			#region Find Earliest Date String
  178.  
  179. 			string pattern = "\\b[12]\\d{3}:[01]\\d:[0-3]\\d [0-2]\\d:[0-5]\\d:[0-5]\\d\\b";
  180. 			Regex regexp = new Regex( pattern, RegexOptions.None );
  181. 			MatchCollection matches = regexp.Matches( header );
  182. 			if ( matches.Count == 0 )
  183. 			{
  184. 				ShowHelp( "No capture date found in file header" );
  185. 				return;
  186. 			}
  187. 			string photodatedelimited = String.Empty;
  188. 			foreach ( Match match in matches )
  189. 			{
  190. 				if ( String.IsNullOrEmpty( photodatedelimited ) || String.Compare( photodatedelimited, match.Value, StringComparison.Ordinal ) > 0 )
  191. 				{
  192. 					photodatedelimited = match.Value;
  193. 				}
  194. 			}
  195. 			photodatedelimited = photodatedelimited.Substring( 0, 10 ).Replace( ":", "-" ) + photodatedelimited.Substring( 10 );
  196. 			string photodatenodelims = photodatedelimited.Replace( ":", "" ).Replace( "-", "" ).Replace( " ", "T" );
  197.  
  198. 			#endregion Find Earliest Date String
  199.  
  200.  
  201. 			if ( settimestamp )
  202. 			{
  203. 				string timeformat;
  204. 				if ( readabletimestamp )
  205. 				{
  206. 					timeformat = "{0:yyyy}-{0:MM}-{0:dd} {0:HH}:{0:mm}:{0:ss}";
  207. 				}
  208. 				else
  209. 				{
  210. 					timeformat = "{0:yyyy}{0:MM}{0:dd}T{0:HH}{0:mm}{0:ss}";
  211. 				}
  212. 				DateTime currenttimestamp = File.GetLastWriteTime( filename );
  213. 				if ( DateTime.TryParse( photodatedelimited, out DateTime newtimestamp ) ) // Try parsing the new file timestamp using the capture timestamp string
  214. 				{
  215. 					if ( DateTime.Compare( currenttimestamp, newtimestamp ) == 0 ) // File and capture timestamps are equal
  216. 					{
  217. 						string photodate = photodatenodelims;
  218. 						if ( readabletimestamp )
  219. 						{
  220. 							photodate = photodatedelimited;
  221. 						}
  222. 						if ( wildcards )
  223. 						{
  224. 							Console.WriteLine( "{0}\t\"{1}\"", photodate, filename );
  225. 						}
  226. 						else
  227. 						{
  228. 							Console.WriteLine( photodate );
  229. 						}
  230. 					}
  231. 					else
  232. 					{
  233. 						double timediff = Math.Abs( ( currenttimestamp - newtimestamp ).TotalSeconds ); // Calculate absolute value of timestamps' difference in seconds
  234. 						if ( timediff > timediffthreshold ) // Ignore time differences up to threshold (default 1 hour, or value set with /D switch)
  235. 						{
  236. 							string blanks = "\n" + new String( ' ', 70 ) + new String( '\b', 70 ); // String to erase the first 70 characters on the next line
  237. 							Console.WriteLine( "Image file name        : {0}", filename );
  238. 							Console.WriteLine( "Current file timestamp : " + timeformat, currenttimestamp );
  239. 							Console.WriteLine( "Capture timestamp      : " + timeformat, newtimestamp );
  240. 							if ( confirmsettimestamp )
  241. 							{
  242. 								Console.Write( "Do you want to change the file's timestamp to the capture time? [yN] " );
  243. 								string answer = Console.ReadKey( ).KeyChar.ToString( ).ToUpper( );
  244. 								if ( answer == "Y" )
  245. 								{
  246. 									File.SetLastWriteTime( filename, newtimestamp );
  247. 									Console.CursorTop -= 1; // Move the cursor 1 line up
  248. 									Console.WriteLine( blanks + "New file timestamp     : " + timeformat, File.GetLastWriteTime( filename ) ); // Overwrite prompt with new timestamp
  249. 									renamecount += 1;
  250. 								}
  251. 								else
  252. 								{
  253. 									Console.WriteLine( "\nskipping . . ." );
  254. 								}
  255. 							}
  256. 							else
  257. 							{
  258. 								File.SetLastWriteTime( filename, newtimestamp );
  259. 								Console.CursorTop -= 1; // Move the cursor 1 line up
  260. 								Console.WriteLine( blanks + "New file timestamp     : " + timeformat, File.GetLastWriteTime( filename ) ); // Overwrite prompt with new timestamp
  261. 								renamecount += 1;
  262. 							}
  263. 						}
  264. 						else
  265. 						{
  266. 							Console.WriteLine( photodatedelimited ); // Timespans' difference is not above threshold (default 1 hour, or value set with /D switch)
  267. 						}
  268. 					}
  269. 				}
  270. 				else
  271. 				{
  272. 					ShowHelp( "Could not determine timestamp of \"{0}\"", filename );
  273. 					return;
  274. 				}
  275. 			}
  276. 			else
  277. 			{
  278. 				Console.WriteLine( "{0}\t\"{1}\"", photodatedelimited, filename );
  279. 			}
  280. 		}
  281.  
  282.  
  283. 		static string Today( bool usedelims = true )
  284. 		{
  285. 			string dateformat;
  286. 			if ( usedelims )
  287. 			{
  288. 				dateformat = "{0:yyyy}-{0:MM}-{0:dd} {0:HH}:{0:mm}:{0:ss}";
  289. 			}
  290. 			else
  291. 			{
  292. 				dateformat = "{0:yyyy}{0:MM}{0:dd}T{0:HH}{0:mm}{0:ss}";
  293. 			}
  294. 			return String.Format( dateformat, DateTime.Now );
  295. 		}
  296.  
  297.  
  298. 		#region Error handling
  299.  
  300. 		public static int ShowHelp( params string[] errmsg )
  301. 		{
  302. 			#region Error Message
  303.  
  304. 			if ( errmsg.Length > 0 )
  305. 			{
  306. 				List<string> errargs = new List<string>( errmsg );
  307. 				errargs.RemoveAt( 0 );
  308. 				Console.Error.WriteLine( );
  309. 				Console.ForegroundColor = ConsoleColor.Red;
  310. 				Console.Error.Write( "ERROR:\t" );
  311. 				Console.ForegroundColor = ConsoleColor.White;
  312. 				Console.Error.WriteLine( errmsg[0], errargs.ToArray( ) );
  313. 				Console.ResetColor( );
  314. 			}
  315.  
  316. 			#endregion Error Message
  317.  
  318.  
  319. 			#region Help Text
  320.  
  321. 			/*
  322. 			CaptureDate,  Version 1.03
  323. 			Return the capture date and time for the specified image file
  324.  
  325. 			Usage:   CAPTUREDATE  image  [ options ]
  326.  
  327. 			Where:   image        specifies the image file(s) (wildcards allowed)
  328.  
  329. 			Options: /D:seconds   minimum Difference in seconds between current file
  330. 			                      timestamp and capture date/time; if the difference
  331. 			                      exceeds the specified number of seconds, the file
  332. 			                      timestamp will be set to the capture date/time
  333. 			                      (default: 3600 seconds = 1 hour; requires /S switch)
  334. 			         /R           Recursive (include subdirectories); you probably want
  335. 			                      to use wildcards for image with this option
  336. 			         /S           Set the image file timestamp to the capture date/time
  337. 			         /T           return the timestamp without "-" and ":" delimiters,
  338. 			                      e.g. 20171114T135628 instead of 2017-11-14 13:56:28
  339. 			         /Y           do not ask for confirmation before changing the image
  340. 			                      file's timestamp (requires /S switch)
  341.  
  342. 			Notes:   Result will be displayed on screen, e.g. 2017-11-14 13:56:28.
  343. 			         The date/time is extracted by searching for the earliest date/time
  344. 			         string in the first 1048576 bytes (1 MB) of the image file.
  345. 			         With /S switch used, the timestamp is changed only if the difference
  346. 			         between the current timestamp and the capture time exceeds 1 hour or
  347. 			         the threshold set with the /D switch.
  348. 			         The program will ask for confirmation before changing the file's
  349. 			         timestamp, unless the /Y switch is used.
  350. 			         Return code ("errorlevel") is -1 in case of errors, or with /S it
  351. 			         equals the number of files renamed, or 0 otherwise.
  352.  
  353. 			Written by Rob van der Woude
  354. 			http://www.robvanderwoude.com
  355. 			*/
  356.  
  357. 			#endregion Help Text
  358.  
  359.  
  360. 			#region Display Help
  361.  
  362. 			string timestampDelimited = Today( true );
  363. 			string timestampNodelims = Today( false );
  364. 			Console.Error.WriteLine( );
  365.  
  366. 			Console.Error.WriteLine( "CaptureDate,  Version {0}", progver );
  367.  
  368. 			Console.Error.WriteLine( "Return the capture date and time for the specified image file" );
  369.  
  370. 			Console.Error.WriteLine( );
  371.  
  372. 			Console.Error.Write( "Usage:   " );
  373. 			Console.ForegroundColor = ConsoleColor.White;
  374. 			Console.Error.WriteLine( "CAPTUREDATE  image  [ options ]" );
  375. 			Console.ResetColor( );
  376.  
  377. 			Console.Error.WriteLine( );
  378.  
  379. 			Console.Error.Write( "Where:   " );
  380. 			Console.ForegroundColor = ConsoleColor.White;
  381. 			Console.Error.Write( "image" );
  382. 			Console.ResetColor( );
  383. 			Console.Error.WriteLine( "        specifies the image file(s) (wildcards allowed)" );
  384.  
  385. 			Console.Error.WriteLine( );
  386.  
  387. 			Console.Error.Write( "Options: " );
  388. 			Console.ForegroundColor = ConsoleColor.White;
  389. 			Console.Error.Write( "/D:seconds" );
  390. 			Console.ResetColor( );
  391. 			Console.Error.Write( "   minimum " );
  392. 			Console.ForegroundColor = ConsoleColor.White;
  393. 			Console.Error.Write( "D" );
  394. 			Console.ResetColor( );
  395. 			Console.Error.Write( "ifference in " );
  396. 			Console.ForegroundColor = ConsoleColor.White;
  397. 			Console.Error.Write( "seconds" );
  398. 			Console.ResetColor( );
  399. 			Console.Error.WriteLine( " between current file" );
  400.  
  401. 			Console.Error.WriteLine( "                      timestamp and capture date/time; if the difference" );
  402.  
  403. 			Console.Error.WriteLine( "                      exceeds the specified number of seconds, the file" );
  404.  
  405. 			Console.Error.WriteLine( "                      timestamp will be set to the capture date/time" );
  406.  
  407. 			Console.Error.Write( "                      (default: 3600 seconds = 1 hour; requires " );
  408. 			Console.ForegroundColor = ConsoleColor.White;
  409. 			Console.Error.Write( "/S" );
  410. 			Console.ResetColor( );
  411. 			Console.Error.WriteLine( " switch)" );
  412.  
  413. 			Console.ForegroundColor = ConsoleColor.White;
  414. 			Console.Error.Write( "         /R           R" );
  415. 			Console.ResetColor( );
  416. 			Console.Error.WriteLine( "ecursive (include subdirectories); you probably want" );
  417.  
  418. 			Console.Error.Write( "                      to use wildcards for " );
  419. 			Console.ForegroundColor = ConsoleColor.White;
  420. 			Console.Error.Write( "image" );
  421. 			Console.ResetColor( );
  422. 			Console.Error.WriteLine( " with this option" );
  423.  
  424. 			Console.ForegroundColor = ConsoleColor.White;
  425. 			Console.Error.Write( "         /S           S" );
  426. 			Console.ResetColor( );
  427. 			Console.Error.WriteLine( "et the image file's timestamp to the capture date/time" );
  428.  
  429. 			Console.ForegroundColor = ConsoleColor.White;
  430. 			Console.Error.Write( "         /T" );
  431. 			Console.ResetColor( );
  432. 			Console.Error.WriteLine( "           return the timestamp without \"-\" and \":\" delimiters," );
  433.  
  434. 			Console.Error.Write( "                      e.g. " );
  435. 			Console.ForegroundColor = ConsoleColor.White;
  436. 			Console.Error.Write( timestampNodelims );
  437. 			Console.ResetColor( );
  438. 			Console.Error.Write( " instead of " );
  439. 			Console.ForegroundColor = ConsoleColor.White;
  440. 			Console.Error.Write( timestampDelimited );
  441. 			Console.ResetColor( );
  442. 			Console.Error.WriteLine( "." );
  443.  
  444. 			Console.ForegroundColor = ConsoleColor.White;
  445. 			Console.Error.Write( "         /Y" );
  446. 			Console.ResetColor( );
  447. 			Console.Error.WriteLine( "           do not ask for confirmation before changing the image" );
  448.  
  449. 			Console.Error.Write( "                      file's timestamp (requires " );
  450. 			Console.ForegroundColor = ConsoleColor.White;
  451. 			Console.Error.Write( "/S" );
  452. 			Console.ResetColor( );
  453. 			Console.Error.WriteLine( " switch)" );
  454.  
  455. 			Console.Error.WriteLine( );
  456.  
  457. 			Console.Error.Write( "Notes:   Result will be displayed on screen, e.g. " );
  458. 			Console.ForegroundColor = ConsoleColor.White;
  459. 			Console.Error.Write( timestampDelimited );
  460. 			Console.ResetColor( );
  461. 			Console.Error.WriteLine( "." );
  462.  
  463. 			Console.Error.WriteLine( "         The date/time is extracted by searching for the earliest date/time" );
  464.  
  465. 			Console.Error.WriteLine( "         in the first 1048576 bytes (1 MB) of the image file." );
  466.  
  467. 			Console.Error.Write( "         With " );
  468. 			Console.ForegroundColor = ConsoleColor.White;
  469. 			Console.Error.Write( "/S" );
  470. 			Console.ResetColor( );
  471. 			Console.Error.WriteLine( " switch used, the timestamp is changed only if the difference" );
  472.  
  473. 			Console.Error.WriteLine( "         between the current timestamp and the capture time exceeds 1 hour or" );
  474.  
  475. 			Console.Error.Write( "         the threshold set with the " );
  476. 			Console.ForegroundColor = ConsoleColor.White;
  477. 			Console.Error.Write( "/D" );
  478. 			Console.ResetColor( );
  479. 			Console.Error.WriteLine( " switch." );
  480.  
  481. 			Console.Error.WriteLine( "         The program will ask for confirmation before changing the file's" );
  482.  
  483. 			Console.Error.Write( "         timestamp, unless the " );
  484. 			Console.ForegroundColor = ConsoleColor.White;
  485. 			Console.Error.Write( "/Y" );
  486. 			Console.ResetColor( );
  487. 			Console.Error.WriteLine( " switch is used." );
  488.  
  489. 			Console.Error.Write( "         Return code (\"errorlevel\") is -1 in case of errors, or with " );
  490. 			Console.ForegroundColor = ConsoleColor.White;
  491. 			Console.Error.Write( "/S" );
  492. 			Console.ResetColor( );
  493. 			Console.Error.WriteLine( " it" );
  494.  
  495. 			Console.Error.WriteLine( "         equals the number of files renamed, or 0 otherwise." );
  496.  
  497. 			Console.Error.WriteLine( );
  498.  
  499. 			Console.Error.WriteLine( "Written by Rob van der Woude" );
  500.  
  501. 			Console.Error.WriteLine( "http://www.robvanderwoude.com" );
  502.  
  503. 			#endregion Display Help
  504.  
  505.  
  506. 			return -1;
  507. 		}
  508.  
  509. 		#endregion Error handling
  510.  
  511. 	}
  512. }
  513.  

page last uploaded: 2021-01-27, 16:12