I have an interesting problem. A client of ours recorded voice conversations from phone calls, but the filename that was given to the recording was invalid. Here is an example of the filename 123:123.wmv
Believe it, Windows Media encoder created the file, and all the information is in the file, however Windows obviously don't recognise the filename, and only display it in the folder as 123
and the file is 0KB
Well from here it is edited: Thanks to Keith Miller that pointed me in the right direction I could write a function that will extract the stream names from the file and use it.
I've included a working copy of how to create two streams of data into a file, read the stream names and read the data from each stream. This is totally awesome, so I hope other people can use this as well. My code ignores the main stream. If you write data into the main stream, it is best if you do not ignore it.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, uGeneralStuff;
type
_FILE_STREAM_INFORMATION = record
NextEntryOffset: cardinal;
StreamNameLength: cardinal;
StreamSize: int64;
StreamAllocationSize: int64;
StreamName: array[0..MAX_PATH] of WideChar;
end;
PFILE_STREAM_INFORMATION = ^_FILE_STREAM_INFORMATION;
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
InfoBlock: _FILE_STREAM_INFORMATION;
StatusBlock : record
Status: Cardinal;
Information: PDWORD;
end;
procedure CreateFile(FileName, Info: String);
function ReadFile(FileName: String): String;
function ReadStreams(filename: String): TStringList;
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
function NtQueryInformationFile(FileHandle : Cardinal;
IoStatusBlock : Pointer;
FileInformation : Pointer;
FileInformationLength : Cardinal;
FileInformationClass : Cardinal
): Cardinal; stdcall; external 'ntdll.dll';
implementation
uses Math, StrUtils;
{$R *.dfm}
function TForm1.ReadStreams(filename: String): TStringList;
var
iFH1: Integer;
aFileName: array[0..MAX_PATH] of WideChar;
aStreamName: String;
begin
Result := TStringList.Create;
iFH1 := FileOpen(filename, GENERIC_READ);
NtQueryInformationFile(iFH1, @StatusBlock, @InfoBlock, SizeOf(InfoBlock), 22); // 22 Means FileStreamInformation
FileClose(iFH1);
while (1=1) do
begin
if InfoBlock.StreamNameLength = 0 then
break;
CopyMemory(@aFileName, @InfoBlock.StreamName, InfoBlock.StreamNameLength);
aStreamName := Copy(aFileName, 1, PosEx(':', aFileName, 2) - 1);
if aStreamName <> ':' then //Ignore main stream, because I know I didn't write data in there
Result.Add(aStreamName);
if (InfoBlock.NextEntryOffset = 0) then
break;
InfoBlock := PFILE_STREAM_INFORMATION(PByte(@InfoBlock) + InfoBlock.NextEntryOffset)^;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
aStreams: TStringList;
I: Integer;
begin
aStreams := ReadStreams('C:\Temp\123');
for I := 0 to aStreams.Count - 1 do
begin
ShowMessage(ReadFile('C:\Temp\123' + aStreams[I]));
end;
end;
procedure TForm1.CreateFile(FileName, Info: String);
var
iFH1: Integer;
Buffer: PAnsiString;
begin
iFH1 := FileCreate(FileName);
Buffer := PAnsiString(AnsiString(Info) + #0);
FileWrite(iFH1, Buffer^, Length(Info));
FileClose(iFH1);
end;
function TForm1.ReadFile(FileName: String): String;
var
iFH1: Integer;
Buffer: PAnsiChar;
iFL: Integer;
iBR, iCurPos, iReadSize: Integer;
begin
iFH1 := FileOpen(FileName, GENERIC_READ);
iFL := FileSeek(iFH1, 0, 2);
FileSeek(iFH1, 0, 0);
iReadSize := Min(iFL, 1024);
Buffer := AllocMem(iReadSize + 1);
iCurPos := 0;
Result := '';
while iCurPos < iFL do
begin
iBR := FileRead(iFH1, Buffer^, iReadSize);
if iBR = -1 then
break;
Result := Result + Buffer;
Inc(iCurPos, iBR);
end;
FileClose(iFH1);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
CreateFile('C:\Temp\123:123.txt', 'This is TestFile 1');
CreateFile('C:\Temp\123:345.txt', 'This is TestFile 2');
ShowMessage(ReadFile('C:\Temp\123:123.txt'));
ShowMessage(ReadFile('C:\Temp\123:345.txt'));
end;
end.
Using a : in a file name creates an alternate data stream in the file. See the article at http://support.microsoft.com/kb/105763
In your example the file is called 123 and the stream is called 123.wmv. You could write a program to extract the stream from the file and rewrite it with a conventional file name.
The article at http://www.flexhex.com/docs/articles/alternate-streams.phtml should help.
FindFirst
uses a TSearchRec
record to return the file properties. There you have a FindData
element (TWin32FindData
), which contains some extra properties like the alternate name of the file. Perhaps you can use that.
Edit: I found a page that contains a unit with a function named ADSFindFirst
(which, incidently, contains NtQueryInformationFile
neatly wrapped internally.) I don't have Delphi here but looks promising: http://www.tek-tips.com/faqs.cfm?fid=7167
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With