Recuerar imagenes jpeg 3

Hace algún tiempo hablé de como recuperar imágenes jpg de un disco o archivo (aquí y aquí), ahora, además de depurar un poco el código de ambos programas, voy a aprovechar para unirlos en uno solo. El código resultante se puede compilar tanto en delphi como en freepascal, por lo que puede ser usado perfectamente en windows o en linux.

El funcionamiento del programa sigue siendo el mismo que el de los dos anteriores, buscamos la marca que indica el comienzo de un archivo jpeg, es decir, los bytes FFD8FF. Una vez encontrada, intentamos obtener de los bytes que siguen a esos tres una imagen jpeg, para luego continuar la búsqueda en el bloque siguiente.

El código es el siguiente (es una aplicación de consola):

program recover;
 
{$APPTYPE CONSOLE}
 
uses
  SysUtils, Classes;
 
type
  EGetOptError = class(Exception);
 
var
  Options: record
    BlockSize: Integer;
    InputFile: String;
    FormatStr: String;
    Index: Integer;
  end;
 
procedure CopyJpegFrom(Src, Dst: TStream);
var
  Buffer: PByteArray;
  Size: Integer;
begin
  GetMem(Buffer,$10000);
  try
    while TRUE do
    begin
      FillChar(Buffer^,4,#0);
      Src.Read(Buffer^,4);
      Size:= ((Buffer[2] shl 8) + Buffer[3]) - 2;
      case Buffer[1] of
        $01,$D0..$D8: begin
                        Dst.WriteBuffer(Buffer^,2);
                        Src.Seek(-2,soFromCurrent);
                      end;
        $D9: begin
               Dst.WriteBuffer(Buffer^,2);
               // Salimos del procedure
               Exit;
             end;
        $DA: begin
               Dst.WriteBuffer(Buffer^,2);
               Src.Seek(-2,soFromCurrent);
               while TRUE do
               begin
                 Src.ReadBuffer(Buffer^,1);
                 if Buffer[0] = $FF then
                 begin
                   Src.ReadBuffer(Buffer^,1);
                  if (Buffer[0] <> 0) and not (Buffer[0] in [$D0..$D7]) then
                   begin
                     Src.Seek(-2,soFromCurrent);
                     break;
                   end else
                   begin
                     Buffer[1]:= Buffer[0];
                     Buffer[0]:= $FF;
                     Dst.WriteBuffer(Buffer^,2);
                   end;
                 end else
                   Dst.WriteBuffer(Buffer^,1);
               end;
             end
        else
        begin
          Dst.WriteBuffer(Buffer^,4);
          Src.ReadBuffer(Buffer^,Size);
          Dst.WriteBuffer(Buffer^,Size);
        end;
      end;
    end;
  finally
    FreeMem(Buffer);
  end;
end;
 
procedure SaveJpegToFile(Src: TStream; Filename: String);
var
  Stream: TMemoryStream;
begin
  Stream:= TMemoryStream.Create;
  try
    CopyJpegFrom(Src, Stream);
    Stream.SaveToFile(Filename);
  finally
    Stream.Free;
  end;
end;
 
procedure ScanFile(Stream: TStream; FormatStr: String; Index: Integer); overload;
var
  B: Byte;
  Leidos: Integer;
  Posicion: int64;
  Str: String;
begin
  // Vamos leyendo byte a byte el archivo
  Leidos:= Stream.Read(B,1);
  while Leidos = 1 do
    if B = $FF then
    begin
      Leidos:= Stream.Read(B,1);
      if Leidos = 1 then
        if B = $D8 then
        begin
          Leidos:= Stream.Read(B,1);
          if Leidos = 1 then
            if B = $FF then
            begin
              // Hasta encontrar la combinacion $FFD8FF
              Writeln('Encontrada una posible imagen');
              // Guardamos la posicion
              Posicion:= Stream.Position;
              Stream.Seek(-3,soFromCurrent);
              Str:= Format(FormatStr,[index]);
              try
                SaveJpegToFile(Stream,Str);
                inc(index);
                Writeln('Recuperada: ' + Str);
              except
                // Si se produce un error lo mostramos, pero no salimos del bucle
                On E: Exception do
                   Writeln('Error: ' + E.Message);
              end;
              // Volvemos a la posicion en la que estabamos
              Stream.Position:= Posicion;
            end;
        end;
    end else
      Leidos:= Stream.Read(B,1);
end;
 
const
  MAXSIZE = 8*1024*1024;
 
procedure ScanFile(Stream: TStream; FormatStr: String; Index: Integer;
  BlockSize: Integer); overload;
var
  Block: PChar;
  Leidos: Integer;
  Posicion: int64;
  Str: String;
  TmpStream: TMemorystream;
begin
  GetMem(Block,BlockSize);
  try
    // Vamos leyendo bloque a bloque el archivo
    Leidos:= Stream.Read(Block^,BlockSize);
    while Leidos = BlockSize do
    begin
      // Hasta encontrar la combinacion $FFD8FF
      if CompareMem(Block,PChar(#$FF#$D8#$FF),3) then
      begin
        Posicion:= Stream.Position;
        TmpStream:= TMemoryStream.Create;
        try
          TmpStream.Write(Block^,Leidos);
          TmpStream.CopyFrom(Stream,MAXSIZE - (MAXSIZE mod BlockSize));
          TmpStream.Position:= 0;
          Writeln('Encontrada una posible imagen.');
          Str:= Format(FormatStr,[index]);
          // Intentamos recuperar la imagen
          try
            SaveJpegToFile(TmpStream,Str);
            inc(index);
            Writeln('Recuperada: ' + Str);
          except
            // Si se produce un error lo mostramos, pero no salimos del bucle
            On E: Exception do
               Writeln('Error: ' + E.Message);
          end;
        finally
          TmpStream.Free;
        end;
        // Nos movemos hasta el siguiente cluster
        Stream.Position:= Posicion;
      end;
      Leidos:= Stream.Read(Block^,BlockSize);
    end;
  finally
    FreeMem(Block);
  end;
end;
 
procedure WriteHelp;
begin
  Writeln;
  Writeln('Uso: recover [opciones]');
  Writeln('  --bs');
  Writeln('    Medida del bloque en el archivo de entrada');
  Writeln('  --fs');
  Writeln('    Cadena para dar formato a las imagenes recuperadas.');
  Writeln('  --help -h');
  Writeln('    Muestra este mensaje de ayuda');
  Writeln('  --id');
  Writeln('    Valor inicial del indice');
  Writeln('  --if');
  Writeln('    Nombre del archivo o disco a explorar'); 
  Writeln;
  Writeln('Ejemplos:');
  Writeln('  recover --if=\\.\F: --bs=4096 --fs=%d.jpg --id=5');
  Writeln('    Busca y repuepar imagenes del disco F, y las guarda');
  Writeln('    como 5.jpg, 6.jpg, ...');
  Writeln('  recover --if=thumb.db --fs=%d.jpg');
  Writeln('    Busca y repuepar imagenes en el archivo "thumb.db", y');
  Writeln('    las guarda como 0.jpg, 1.jpg, ...');
  Writeln;      
end;
 
function Starts(Sub, Str: String): Boolean;
begin
  Result:= SameText(Sub,Copy(Str,1,Length(sub)));
end;
 
procedure Getopt;
var
  i: Integer;
  Str: String;
begin
  FillChar(Options,Sizeof(Options),#0);
  if ParamCount > 0 then
    for i:= 1 to ParamCount do
    begin
      Str:= ParamStr(i);
      if (Copy(Str,1,2) = '--') and (Length(Str) > 2) then
      begin
        if Starts('--if=',Str) then
        begin
          if Options.InputFile = EmptyStr then
            Options.InputFile:= Copy(Str,Length('--if=') + 1,MAXINT)
          else
            raise EGetOptError.Create(Str);
        end else if Starts('--id=',Str) then
        begin
          if Options.Index = 0 then
            Options.Index:= StrToInt(Copy(Str,Length('--id=') + 1,MAXINT))
          else
            raise EGetOptError.Create(Str);
        end else if Starts('--fs=',Str) then
        begin
          if Options.FormatStr = EmptyStr then
            Options.FormatStr:= Copy(Str,Length('--fs=') + 1,MAXINT)
          else
            raise EGetOptError.Create(Str);
        end else if Starts('--bs=',Str) then
        begin
          if Options.BlockSize = 0 then
            Options.BlockSize:= StrToInt(Copy(Str,Length('--bs=') + 1,MAXINT))
          else
            raise EGetOptError.Create(Str);
        end else if SameText('--help',Str) then
        begin
          WriteHelp;
          Halt;
        end else
          raise EGetOptError.Create(Str);
      end else if (Copy(Str,1,1) = '-') and (Length(Str) > 1) then
      begin
        if SameText('-h',Str) then
        begin
          WriteHelp;
          Halt;
        end else
          raise EGetOptError.Create(Str);
      end else
      begin
        raise EGetOptError.Create(Str);
      end;
    end else
    begin
      WriteHelp;
      Halt;
    end;
end;
 
procedure WriteHeader;
begin
  Writeln;
  Writeln('Recover 0.2 - Copyright (c) 2007 Domingo Seoane');
  Writeln('<a href="http://delphi.jmrds.com'">http://delphi.jmrds.com'</a>);
  Writeln;
end;   
 
var
  Stream: TFileStream;
begin
  WriteHeader;
  try
    GetOpt;
    TFileStream.Create(Format(Options.FormatStr,[0]),fmCreate).Free;
    DeleteFile(Format(Options.FormatStr,[0]));
    Stream:= TFileStream.Create(Options.InputFile,fmOpenRead or
      fmShareDenyNone);
    try
      Writeln('Explorando el archivo: ' + Options.InputFile);
      if Options.BlockSize > 0 then
        ScanFile(Stream,Options.FormatStr,Options.Index,Options.BlockSize)
      else
        ScanFile(Stream,Options.FormatStr,Options.Index);
    finally
      Stream.Free;
    end;
  except
    // Si se produce un error, lo mostramos
    On E: Exception do
      Writeln('Error: ' + E.Message);
  end;
end.

El código completo y el programa ya compilado para windows, los puedes bajar de aquí

Para compilar el código en linux con freepascal utiliza un comando como este:
fpc -XX -Sd recover.dpr

El programa recibe cuatro parámetros de los que solo dos son obligatorios, --if y --fs. El archivo de entrada (--if) es el archivo o disco donde se buscaran las imágenes, puede ser una archivo normal, por ejemplo "thumb.db", o un disco, por ejemplo "\\.\F:" en windows o "/dev/sda" en linux. El parámetro --fs sirve para generar el nombre con el que se guardaran las imágenes recuperadas, debe contener la cadena %d que sera sustituida por un numero creciente. Los otros dos parámetros (--bs y --id) son opcionales, y sirven para indicar el tamaño del bloque (necesario para los discos duros) y el numero por el que se empiezan a numerar las imágenes recuperadas.

Algunos ejemplo de uso:
recover --if=\\.\F: --bs=4096 --fs=%d.jpg
recover --if=/dev/sda --bs=4096 --fs=%d.jpg
recover --if=thumb.db --fs=%d.jpg --id=100

Comentarios

I think that this program also recover actual (non deleted) images. In fact it recover all possible jpg images from disk. On a disk i have about 3000 jpg images and it recover all of them + many miniatures. Am i right?

Thanks. nh.

Tienes razón, el programa recupera todas las imágenes jpg que encuentra en el disco, sin importar si las imágenes han sido borradas o no. Esto es un problema cuando tienes muchas imágenes en un disco, una posible mejora sería descartar las que ya existen, o incluso hacer algún tipo de vista previa para poder seleccionar las que se quieren recuperar, pero eso lo dejo para un futuro.

gracias - a screening or filter would be not easy to do since we have no name on the files. Regular restoration programs recover deleted file by name i guess. Here it is different. Thank again - from montreal canada

Cordial saludo Domingo.

Ojalá aún sigas atendiendo tu sitio.

Estas a punto de resolverme una necesidad grande que tengo, o tenía.

Estaba ampliando un partición de mi disco y hubo un corte de energía, cuando volví a tratar de ver su contenido (al rededor de 10000 fotos y videos), no pude verlas. Intenté recuperarlas con varios programas que hay en el mercado (Easyrecory, GetdataBack) y ecuperó una buena parte, pero tengo pendiente recuperara al rededor de 3000.

Tu programa me viene como anillo al dedo. Sin embargo tengo algunas inquietudes al respecto:

1. Dado que se trata de hacer un barrido de un dd de 250G, consideras viable utilizarlo ?. No importa que se demore días en lograrlo.
2. Revisé los fuentes y se ocurre que utilizarlo para otro tipo de archivos sólo requiere conocer la "firma" del tipo de archivo que se desea recuperar y hacer los ajustes a tu fuente. Es correcto ?

Es inmensa la gratitud que te manifiesto, sólo por darme la esperanza de poder recuperar los archivo.

Te felicito por el programa, lo que no entiendo si detecta los distintos formatos de disco (Fat32,nfts,...). Aunque veo que aguanta los formatos de linux.

Supongo que según el formato se tendría que reajustar los parámetros.

Que al final se pueda usar a base de comandos linux, lo hace una herramienta muy eficaz. Por ejemplo en php podemos recurrir a este programa en la consola.

Lee cualquier formato de disco ya que lee "físicamente" el disco, independientemente del sistema de ficheros que se este utilizando.