Как-то я писал про ужасные беды и в том же посте предположил, что было бы очень занимательно автоматизировать процесс: большие мальчики против хэндджоба.
Для получения готового mkv-контейнера, dts трэк из источника должен был пройти через несколько консольных экзекуций.
Цикл обработки сводился к следующему:
- Определяем индекс трэка DTS
- Достаем его из контейнера в отдельный файл
- Конвертируем в двухканальный WAV
- Конвертируем WAV в MP3
- Склеиваем трэки из оригинального контейнера и получившийся MP3 файл, выбрав его дефолтным
- ????????
- Имеем ПРОФИТ
- mkvinfo.exe
- mkvextract.exe
- valdec.exe из комплекта AC3Tools
- lame.exe
- mkvmerge.exe
- chmod -r 777 /mnt/hands
- profit.exe
Для получения консольного вывода можно использовать файлы, выполняя команду "console_blah.exe >OutFileName.txt", но это моветон и никак не комильфо. Решено было использовать Pipes(далее пайпы). Пайпы - это хитрые каналы, которые и были разработаны для подобных гнусных целей.
В задачу не входило создание унифицированного класса, но без финта ушами было бы не интересно. В итоге получился базовый класс, в задачу которого входит создание процесса, пайпа и присоединение второго к первому.
Type TConsoleThread = class (Tthread) //создаем потомок класса TThread, тащемта поток private FFileName: string; // путь и имя консольной шурудилки FParamStr: string; // параметры запуска на орбиту FProcInfo: TProcessInformation; // при создании процесса получает информацию об оном FStartupInfo:TStartupInfo; // показывает CreateProcess информацию для запуска FSecurityAttr:TSecurityAttributes; // параметры безопасности для пайпа FReadHandle, FWriteHandle: THandle; // Хэндлы для пайпа FExitProc:TnotifyEvent; // ебаный стыд, который к тому же не работает, оставил для суровости procedure AppendOut(S:String); virtual; // процедура добавления буфера консольного вывода в общую кучу procedure SetExitProc(Value:TnotifyEvent); // опять ебаный стыд protected procedure Execute; override; // выполнение потока, запускается после создания public constructor Create(CreateSuspended: Boolean; const Filename,ParamStr: string); // создание потока property ExitProcedurePointer:TnotifyEvent read FExitProc write SetExitProc; // под конец все тот же ЁС end; // кончел
constructor TConsoleThread.Create(CreateSuspended: Boolean;const Filename,ParamStr: string); begin inherited Create(CreateSuspended); // родительская процедура создания класса FExitProc:=nil; // убираем ЁС FFileName:=Filename; // берем имя из параметров FParamStr:=ParamStr; // берем параметры из параметров (каламбурчик) with FSecurityAttr do begin nLength := SizeOf(TSecurityAttributes); lpSecurityDescriptor := nil; bInheritHandle := true; //делаем указатели наследуемыми end; CreatePipe(FReadHandle, FWriteHandle, @FSecurityAttr, 0); // создаем пайп SetHandleInformation(FReadHandle, HANDLE_FLAG_INHERIT, 0) ; // очередное наследование хэндла, зачем - неизвестно GetStartupInfo(FStartupInfo); // получаем структуру with FStartupInfo do begin wShowWindow := SW_HIDE; // не показывать консоль dwFlags := dwFlags or STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES; // последнее важно для наследование хэндлов hStdOutput := FWriteHandle; // перенаправляем вывод в пайп hStdError := FWriteHandle; // ошибки туда же ( lame выводит информацию именно в hStdError) end; CreateProcess(pChar(FFileName),pChar(' '+FParamStr), nil, nil, true, CREATE_UNICODE_ENVIRONMENT, nil, nil, FStartupInfo, FProcInfo); // создаем процесс. помни, воен, что bInheritHandles:=true end;
Процесс и пайп создаются, все очень круто связывается друг с другом и, казалось, на этом можно и закончить, но нет, надо еще и буквы получать.
procedure TConsoleThread.Execute; var k:cardinal; // тут будет код завершения процесса ReadBuf:array[0..1000] of AnsiChar; // буфер для строки, аблизательно AnsiChar BytesRead,TotalB: Cardinal; // тут будет количество прочитаных байт и оставшихся в пайпе Finish:Boolean; begin repeat Sleep(10); FillChar(ReadBuf,High(ReadBuf),0); // очищаем буфер if PeekNamedPipe(FReadHandle, @ReadBuf[0], High(ReadBuf)-1, @BytesRead,@TotalB, nil) then- // проверяем пайп на наличие информации. можно было сразу читать, но процедура чтения ассинхронная
- // и если информации в пайп не поступит то все повиснет как. повиснет вообщем.
begin if TotalB>0 then // если есть что читать if ReadFile(FReadHandle, ReadBuf, High(ReadBuf)-1, BytesRead, nil) then // если прочитали if BytesRead > 0 then // и если даже есть буквы begin ReadBuf[BytesRead] := #0; // завершаем строку AppendOut(ReadBuf); // добавляем в кучу обзего вывода end; GetExitCodeProcess(FProcInfo.hProcess, k); // периодически опрашиваем процесс на живучесть Finish:=(k<>STILL_ACTIVE) and (TotalB=0); // если процесс завершился и в буферах кончились буквы- // то мы на финише
end; until Finish; // финиш ли CloseHandle(FProcInfo.hProcess); // кончил - оботри станок- CloseHandle(FReadHandle);
- CloseHandle(FWriteHandle);
- end;
и даже после этого мы не получим заветных буков, потому что нет у нас процедуры которая добавляла бы их к общему выводу. А вот и она:
procedure TConsoleThread.AppendOut(S: string); begin // заглушка end;
Сиё унылое говно имеет место быть лишь потому, что для данного класса необходимо только создать процесс, пайп, прилинковать их, прокрутить вывод и весело завершиться.
Для чтения создаем дочерний класс:
Type TConsoleOutputThread = class (TConsoleThread) private FStrings: TStrings; // будем тут строки хранить procedure SetOutput(Value: TStrings); // неиллюзорно привязываемся в компоненту на форме procedure AppendOut(S:String); override; // перекрывающая функция обработки буфера public property OutputStrings: TStrings read FStrings write SetOutput; // публичная привязка, о как end;
При создании нового экземпляра класса TConsoleOutputThread мы присвоим ему компонент, в который он будет скидывать буквы.
ParserOut:=TConsoleOutputThread.Create(false,ExtractFilePath(ParamStr(0))+MKVINFO,'"'+filename+'"');- //создаем экземпляр класса TConsoleOutputThread
ParserOut.FreeOnTerminate:=true;- // проперти из TThread указывает что после экзекуции процесс должен почистить за собой
ParserOut.OutputStrings:=Outp;- // Outp был объявлен и создан раньше (КО). Вместо него можно сунуть Memo1.lines
Осталось только дописать процедуры привязки класса/компонента для вывода информации и собственно процедуру вывода в него строк (не хотит кодом вставлять, процитируем)
procedure TConsoleOutputThread.SetOutput(Value: TStrings);
begin
FStrings:= Value; //привязываем компонент для вывода
end;procedure TConsoleOutputThread.AppendOut(S: string);
begin
FStrings.Text:=FStrings.Text+S; // добавляем буфер к имеющемуся тексту
end;
Как результат: получаем уютненький построчный вывод, причем не после завершения консольного приложения, а во время его выполнения.
В подробности вдаваться не буду, скажу только что долго рыл всяческие дельфикондом, кодерзком и мсдн. Только с мсдн что-то более менее прояснилось.
Полученную утилиту сразу применил в деле. Буду теперь смотреть грусный фильм про собачку на большом экране, рыдать и дергать пинус
PS: Картинка в начале является де-факто бложиков, без нее на пост и смотреть бы не стали. Вот такое вероломное оно, первое что выдал яндекс на слово "консоль".
PS1: По-хорошему можно прилинковать вывод одной консольной программы к вводу другой, так все будет быстрее и многопоточно. но лень