The iPod Nano is a beautiful thing. That is unless you want to do something custom. So it is with the lap timer in it. There is no obvious way of synchronising the timer logs with the computer. Even if there is a way in ITunes (I’m pretty sure there isn’t), the point is moot, as I don’t use ITunes (instead I’m using the wonderful ml_ipod plugin for Winamp). This is “suboptimal” if you want to, for example, use the lap timer to chart your jogging progress over the course of a year. You want to crunch those stats in Excel or some such, you certainly aren’t going to copy them from the screen.
The information on the web is a little sparse on this matter, but there are a number of applications that will let you do this. The one that caught my eye was http://wafflesoftware.net/ipodtimer/ which luckily provided source code (Mac OS X only – aren’t we feeling exclusive). A relatively trivial exercise in parsing. The timer format was loosely described in the TimerFormat.txt in the zipped source. At least detailed enough for me to whip up an implementation in C#. Hence this post.
The underlying format is a binary file. Time to dust off .NET’s BinaryReader. The below method ReadTimerEntries(string filepath) will read in an iPod timer file (on my Nano it’s /IPod_Control/device/timer) and produce a list containing the date of the recording, and the associated laps. From there you have all the info you need. Anyway, it works for me, on an iPod Nano (3rd gen). I don’t imagine that the file structure changes much though.
- public partial class Form1 : Form
- {
- public Form1()
- {
- InitializeComponent();
- }
- private void button1_Click(object sender, EventArgs e)
- {
- OpenFileDialog ofd = new OpenFileDialog();
- ofd.FileName = Application.StartupPath;
- DialogResult dr =ofd.ShowDialog();
- if (dr == DialogResult.OK)
- {
- List<TimeEntry> entries = ReadTimerEntries(ofd.FileName);
- foreach (TimeEntry entry in entries)
- {
- System.Diagnostics.Debug.WriteLine(entry.EntryDate.ToString());
- foreach (TimeSpan ts in entry.Laps)
- {
- System.Diagnostics.Debug.WriteLine(ts.ToString());
- }
- }
- }
- }
- public List<TimeEntry> ReadTimerEntries(string filePath)
- {
- List<TimeEntry> results = new List<TimeEntry>();
- //open the file
- var stream = File.OpenRead(filePath);
- BinaryReader br = new BinaryReader(stream);
- br.ReadBytes(4); //start file
- while (!br.ReadBytes(4).SequenceEqual(new byte[] { 0x13, 0, 0, 0xC0 }))
- {
- //first 4 bytes are already consumed
- ReadRound(br, results);
- }
- return results;
- }
- private void ReadRound(BinaryReader br, List<TimeEntry> itemList)
- {
- br.ReadBytes((16 * 5));// guff
- br.ReadBytes(8);// start date
- byte seconds = br.ReadByte();
- byte minutes = br.ReadByte();
- byte hour = br.ReadByte();
- byte day = br.ReadByte();
- byte month = br.ReadByte();
- int year = br.ReadInt16();
- br.ReadByte(); //unknown use
- TimeEntry entry = new TimeEntry();
- entry.EntryDate = new DateTime(year, month, day, hour, minutes, seconds);
- itemList.Add(entry);
- br.ReadBytes(4); //end date
- br.ReadBytes(4); //start laps
- while (!br.ReadBytes(4).SequenceEqual(new byte[] { 0x10, 0, 0, 0xC0 }))
- {
- br.ReadBytes(4); //start lap
- int lapMilliseconds = br.ReadInt32(); //4 bytes
- TimeSpan lapTime = TimeSpan.FromMilliseconds(lapMilliseconds);
- entry.Laps.Add(new TimeSpan(0, 0, 0, 0, lapMilliseconds));
- br.ReadBytes(4); //end lap
- }
- br.ReadBytes(4); //end round
- }
- }
- public class TimeEntry
- {
- public DateTime EntryDate;
- public List<TimeSpan> Laps = new List<TimeSpan>();
- }