Wednesday, August 26, 2009

Recovering JPEG images from a damaged compact flash card

I've started to blog again...

I've not been lucky with data storage devices recently.

Last week my Thinkpad crashed. The disk got shredded into pieces under the Linux /lost+found directory. I had to crawl through it for two days to manually recover some of the files I cared about.

A few weeks ago the compact flash card in my digital camera got damaged too, giving me a "This card cannot be used" error message. I had two 2 GB of JPEG images on that card: family photos that I didn't want to lose! After reading the troubleshooting section of my Nikon's user manual I was almost ready to buy some expensive recovery software, or ship my card to a company that recovers images, when I thought:

"Wait... I'm a software developer. How about hacking a little program to recover those images myself?" That's what I did, and it was surprisingly easy! so I thought I'd share that recipe here.

If your compact flash card ever gets damaged (and from what I see on the Web that happens a lot), try the following:
  • Save a snapshot of your card in a file. On Linux do: dd if=/dev/<card-device> of=<card-file> bs=512

  • Read that file in chunks of 512 bytes, as the card is usually organized in sectors of 512 bytes.
     
  • If you see hex 0xff 0xd8 0xff 0xe1 at the beginning of a sector, you've hit the JPEG/Exif header of a JPEG image. If you're wondering what I'm talking about, just Google it or check the Wikipedia JPEG and Exif pages.

  • If the card is not too fragmented, most of your JPEG images are stored in contiguous sectors. Save that sector and the next ones into a .JPG image file until you hit the next JPEG header, save the next image, and go on until you reach the end of the card. You'll save up to 511 bytes of garbage at the end of each image file but that's OK, as JPEG viewers use the length of JPEG data encoded in the header and will ignore that garbage at the end of the file.
Here's a little C program translation of the above algorithm:

recover-jpg.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

/* Returns true if a sector contains a JPEG image header. */
const int isJPEG(const unsigned char *sector) {
  return(sector[0] == 0xff && sector[1] == 0xd8 && sector[2] == 0xff && sector[3] == 0xe1);
}

/* Buffer used to read sectors from the card */
unsigned char buffer[512];

/* Returns the first sector of the first JPEG image on a card file. */
const unsigned char* firstJPEG(FILE *card) {
  if (fread(buffer, 512, 1, card) != 1)
    return NULL;
  if (isJPEG(buffer))
    return buffer;
  return firstJPEG(card);
}

/* Recovers contiguous sectors containing the pieces of a JPEG image. */
const unsigned char* recoverSector(FILE* card, const unsigned char* sector, FILE* image) {
  /* write sector to the image file */
  fwrite(sector, 1, 512, image);

  /* read the next sector */
  if (fread(buffer, 512, 1, card) != 1)
    return NULL;

  /* done with this image, we've hit the next JPEG header */
  if (isJPEG(buffer))
    return buffer;

  /* recover the next sector from this image */
  return recoverSector(card, buffer, image);
}

/* Recovers JPEG images from a card file. Returns the number of recovered images. */
const int recoverJPEG(FILE* card, const unsigned char* firstSector, const int imageCount) {
  /* no sector to recover, we're done */
  if (firstSector == NULL)
    return imageCount;

  /* create JPEG image file */
  char path[16];
  sprintf(path, "DSC_%4.4d.JPG", imageCount);
  printf("Recovering %s\n", path);
  FILE* image = fopen(path, "wb");

  /* recover sectors from that image */
  const unsigned char* nextSector = recoverSector(card, firstSector, image);

  /* close image file */
  fclose(image);

  /* recover the next image */
  return recoverJPEG(card, nextSector, imageCount + 1);
}


int main(const int argc, const char *argv[]) {
  if(argc != 2) {
    printf("Usage: %s card-file\nto recover JPEG images from card-file.\n", argv[0]);
    printf("Card-file can be created like this:\ndd if=/dev/<card-device> of=<card-file> bs=512\n\n");
    return(1);
  }

  /* open card file */
  FILE *card = fopen(argv[1], "rb");
  if (card == NULL) {
    printf("Could not open %s\n", argv[1]);
    return 1;
  }

  /* recover JPEG images from it */
  const int imageCount = recoverJPEG(card, firstJPEG(card), 0);

  printf("Recovered %d images!\n\n", imageCount);

  /* close card file */
  fclose(card);

  return 0;
}


To build that program (on Linux Ubuntu 9.0.4) do: gcc -O2 recover-jpg.c -o recover-jpg

A side comment: With the -O2 option, the Linux GCC C compiler optimizes sibling calls, allowing you to use tail recursion instead of loops without growing the stack. That's what I did in the firstJPEG(), recoverJPEG() and recoverSector() functions, just for fun :)

To run the program: recover-jpg <your-card-file>

It'll save the JPEGs found on the card into files named DSC_0000.JPG, DSC_0001.JPG etc in the current directory.

That program successfully recovered the 700 JPEG images stuck on my compact flash card! I hope it'll work for you too if your card ever gets damaged...

Please drop me a comment here if you find any bugs in this little piece of code. Thanks!

1 comment:

Ryan said...

This worked like a charm for the damaged card of a friend of mine. I was thinking of writing some code, and then I thought, "you know, I bet someone else has already done this!" Thanks so much for sharing and making this easy for me!


The postings on this site are my own and don’t necessarily represent positions, strategies or opinions of my employer IBM.