2008-06-16

My M4A2MP3 script

I have a Sandisk Sansa MP3 player that I picked up on Woot.com for a song. It's a refurbished unit that has a couple little quirks, but for the most part, I love it. My primary praise for the thing is how easy it is to transfer files to. It identifies itself as a simple mass storage device, so all I have to do is drag and drop my music files to it. And I'm done. Contrast this with the iPod my mother got for Christmas, who after hours of frustration had to call her sister's son-in-law over for help in installing the iTunes software (which has been known to install other software behind your back; but we won't get into that here). Yes, the Sansa can be used in an alternate mode that does an auto-sync thing with Windows Media Player, but I chose not to go with that more confusing route. The Sansa does have a converter that you must use in order to do video, but when it comes down to it, I just don't find video on a 2" screen worth the hassle anyway.

One thing I do wish it had was a bookmarking function, as I like to listen to audiobooks; and if you lose your place in a 40-hour audio file, trying to find it again is frustrating. But since I've taken to using Goldwave to splitting my audiobooks into 1-hour files before transfer (and since playlists are in a standard, text-based format and very easy to create to "bind" the parts together), it's much less of an issue.

Anyway, the purpose of this post isn't so much to praise the Sansa, but to describe one workaround for a common trend. I've noticed a start of a shift from MP3s to M4As in audio, especially in podcasts. Xbox Live's Major Nelson did it for one episode (although he went back to MP3s afterward), and only the first GeezerGamers.com podcast was available in MP3. The Sansa, unfortunately, does not natively support M4A, just MP3 and WMA. Since I'm not yet ready to install Rockbox on it, I had to find another way.

I did some searching on converting M4A to MP3, and I found a pretty simple script here that does the conversion. It does, however, presuppose install paths for your programs (it launches two command-line utilities, FAAD and LAME, to convert to WAV and MP3, respectively). I decided I wanted to put it in a folder on my Sansa (since it's just a USB storage device, it can hold anything) and make it available no matter how it was assigned when it got plugged into a machine.

I have a folder, called M4A2MP3, that contains faad.exe, lame.exe, lame_enc.dll, and m4a2mp3.bat. The contents of the batch file are as follows:

echo off
REM Simple script to convert m4a to mp3, got it from http://pieter.wigleven.com/it/archives/3
REM With edits by Yakko Warner, http://yakkowarner.blogspot.com/2008/06/my-m4a2mp3-script.html
if /I "%~x1" NEQ ".m4a" (
    echo Warning, file doesn't look like an m4a: %~nx1
    pause
)
cls
echo.
echo Converting %1 to MP3
"%~dp0\faad.exe" -o "%TEMP%\%~nx1.wav" %1
"%~dp0\lame.exe" --preset standard "%TEMP%\%~nx1.wav" "%~dpn1.mp3"
del "%TEMP%\%~nx1.wav"

Being a simple script, it does require you have enough space wherever %TEMP% lives, but it does have several advantages over the original script. It uses %TEMP% as the location of the .wav file, which is typically faster, local storage; it replaces the .m4a extension with .mp3 instead of appending it; it allows the executables and script to travel together in a single folder; and no paths are hard-coded. It also, if you give it a file that doesn't have an .m4a extension, alerts you of this fact and presents you an opportunity to ^C and cancel the script.

To execute, it's as easy as dragging an .m4a file onto the batch file and watching it work. Note that it is possible to install this to your hard drive and add to Explorer's right-click menu for M4A files to run this script. I've set that up once, but considering how infrequently I have to convert M4As in general (usually just once a week), it hasn't quite been worth doing that on a regular basis.

Cleaning Temp on logoff

I've noticed that the Temp directory in Windows tends to get a little full, and it doesn't clean itself out. I find this rather odd, as it can eat up a lot of space. While I can't prove that it degrades performance or anything like that, it is a little housekeeping that keeps the more obsessive/compulsive side of myself happy.

There are a couple problems, though. One is just remembering to do it. The other is coming up with a convenient way to do it. If you've ever tried to delete these files by hand, you'll notice that Explorer will spend a lot of unnecessary time enumerating the files it's about to delete, and the first file that's actually in use will cause the entire operation to fail. The easiest way to accomplish the goals and get around the problem of locked files is to use a logoff script that is coded to skip or ignore those files.

Once upon a time, I wrote a VBScript file that would systematically delete each file, one by one. With the dreaded On Error Resume Next statement, I could skip the files that were locked without crashing the script. The added bonus was that I could write code very easily to skip certain files. There is an XP PowerToy that manages 4 virtual desktops, each with its own wallpaper; but it stores the wallpaper for each desktop in Temp, and expects that file to be there even across reboots. The huge drawback to this, of course, is that deleting files one at a time is extremely slow.

I've since decided that I don't use the virtual desktops, so there's no sense in installing that app. And, what I was doing in VBScript could be done with two lines of shell script that runs much faster. Since I always have to look up the reference for batch files and variables and what-not every time I try to create this file, I decided it was time to put it in "extended memory" (i.e. a blog post, where I could find it later).

del %TEMP%\*.* /s /f /q
for /d %%x in (%TEMP%\*) DO rmdir /s /q "%%x"

The first line deletes all files in all subdirectories at and below %TEMP%, without prompting. The second line then iterates all the directories and attempts to remove them, again without prompting.

Getting this to run on logoff isn't something that I'll forget, but for others' benefit, here you go. Note that this is only in Windows XP and 2000; I have not yet figured out the Vista equivalent.

  • Run the Group Policy Management Console (Start, Run, "gpedit.msc")
  • Drill down to "User Configuration\Windows Settings\Scripts (Logon/Logoff)"
  • Double-click Logoff
  • Click "Show Files" to open an Explorer window to where the files go, and copy the batch file there
  • Click "Add" and add the batch file to the script list

I have not yet had an issue with this, but it's always possible something could go wrong, so use at your own risk. :P

2008-06-03

DataGridView style inheritance

Interesting "feature" in .Net Windows Forms 2.0 DataGridView. We have a DGV that is initialized in code. At the top of the initialization code is this statement:

dgv.RowsDefaultCellStyle.ForeColor = Color.Black;
dgv.DefaultCellStyle.SelectionBackColor = Color.MediumBlue;
dgv.DefaultCellStyle.SelectionForeColor = Color.White;

Then, columns are created and added:

DataGridViewTextBoxColumn c = new DataGridViewTextBoxColumn();
c.Name = "Foo";
...
c.DefaultCellStyle.Format = "0.00";
c.DefaultCellStyle.ForeColor = Color.Red;
c.DefaultCellStyle.SelectionForeColor = Color.Red;
dgv.Columns.Add(c);

Guess what? The text is not red. Unless it's selected, then it is red. In order to force the text to turn red, I have to do this, after the DataSource is set:

foreach (DataGridViewRow r in dgv.Rows) {
   r.Cells["Foo"].Style.ForeColor = Color.Red;
}

Apparently, the column's DefaultCellStyle is ignored. Actually, it's overridden by the grid's RowsDefaultCellStyle, which is exactly backwards of what I would expect (RowsDefaultCellStyle and AlternatingRowsDefaultCellStyle should be higher up the override hierarchy, because they're more generic, describing an unbound range of rows, than a specific column's DefaultCellStyle).

What's even more frustrating, is that sorting the grid causes the individual styles to be conveniently forgotten. So, I have to add that same loop (or, to be more proper, move the loop to a function, call it from the DataBind event, and add a call...) to the DataGridView's Sorted event.

This was one of those problems that, once I figured out what was going on, only then was I able to google and find the articles that described it. And apparently it's not new, just news to me. I still find it backwards, requiring a whole lot of extra work to make it behave the right way; and thus I reserve the right to complain.