ADD: Simple m4a parser

This commit is contained in:
Alexander Koblov 2016-04-23 20:01:12 +00:00
commit 6ea2eb3494
3 changed files with 234 additions and 2 deletions

View file

@ -10,7 +10,7 @@ const
DETECT_STRING: String = '(EXT="MP3") | (EXT="MP2") | (EXT="MP1") | (EXT="OGG") | (EXT="WMA") | ' +
'(EXT="WAV") | (EXT="VQF") | (EXT="AAC") | (EXT="APE") | (EXT="MPC") | ' +
'(EXT="FLAC") | (EXT="CDA") | (EXT="TTA") | (EXT="AC3") | (EXT="DTS") | ' +
'(EXT="WV") | (EXT="WVC") | (EXT="OFR") | (EXT="OFS")';
'(EXT="WV") | (EXT="WVC") | (EXT="OFR") | (EXT="OFS") | (EXT="M4A")';
const
FIELD_COUNT = 20;

View file

@ -0,0 +1,211 @@
{
Double commander
-------------------------------------------------------------------------
Class TMP4file - for manipulating with M4A audio file information
Copyright (C) 2016 Alexander Koblov (alexx2000@mail.ru)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
}
unit MP4file;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, DCClassesUtf8;
type
TAtomName = Array [0..3] of AnsiChar;
{ TMP4file }
TMP4file = class
private
FFileSize: Int64;
FStream: TStream;
function GetValid: Boolean;
private
FChannels: Word;
FBitRate: Double;
FDuration: Double;
FSampleSize: Word;
FSampleRate: LongWord;
protected
procedure ResetData;
procedure ReadMovieHeader;
procedure ReadSampleDescription;
function FindAtomHeader(const AName: TAtomName; ASize: PInt64 = nil): Boolean;
function LoadAtomHeader(out AtomName: TAtomName; out AtomSize: Int64): Boolean;
public
constructor Create; { Create object }
destructor Destroy; override; { Destroy object }
function ReadFromFile(const FileName: String): Boolean; { Load header }
property FileSize: Int64 read FFileSize; { File size (bytes) }
property Channels: Word read FChannels; { Number of channels }
property SampleRate: LongWord read FSampleRate; { Sample rate (hz) }
property BitRate: Double read FBitRate; { Bit rate (bit/s) }
property Duration: Double read FDuration; { Duration (seconds) }
property Valid: Boolean read GetValid; { True if data valid }
end;
implementation
{ TMP4file }
function TMP4file.FindAtomHeader(const AName: TAtomName; ASize: PInt64): Boolean;
var
AtomSize: Int64;
APosition: Int64;
AtomName: TAtomName;
begin
repeat
if not LoadAtomHeader(AtomName, AtomSize) then
Break;
if SameText(AtomName, AName) then
begin
if Assigned(ASize) then ASize^:= AtomSize;
Exit(True);
end
else begin
APosition:= FStream.Seek(AtomSize, soCurrent);
end;
until (APosition >= FFileSize);
Result:= False;
end;
function TMP4file.GetValid: Boolean;
begin
Result:= (FDuration > 0.0) and (FBitRate > 0.0);
end;
function TMP4file.LoadAtomHeader(out AtomName: TAtomName; out AtomSize: Int64): Boolean;
begin
AtomSize:= SwapEndian(FStream.ReadDWord);
FillChar(AtomName, SizeOf(TAtomName), #0);
FStream.Read(AtomName, SizeOf(TAtomName));
if AtomSize <> 1 then
AtomSize:= AtomSize - 8
else begin
AtomSize:= Int64(SwapEndian(FStream.ReadQWord)) - 16;
end;
Result:= not ((AtomSize < 0) or (AtomSize > FFileSize));
end;
procedure TMP4file.ResetData;
begin
FBitRate:= 0;
FChannels:= 0;
FDuration:= 0.0;
FSampleSize:= 0;
FSampleRate:= 0;
end;
procedure TMP4file.ReadMovieHeader;
var
AVersion: Byte;
MediaSize: Int64;
ADuration: QWord;
TimeScale: LongWord = 0;
begin
FStream.Seek(0, soBeginning);
if FindAtomHeader('moov') and FindAtomHeader('mvhd') then
begin
AVersion:= FStream.ReadByte;
FStream.Seek(3, soCurrent);
if AVersion = 0 then
begin
FStream.Seek(8, soCurrent);
TimeScale:= SwapEndian(FStream.ReadDWord);
ADuration:= SwapEndian(FStream.ReadDWord);
end
else if AVersion = 1 then
begin
FStream.Seek(16, soCurrent);
TimeScale:= SwapEndian(FStream.ReadDWord);
ADuration:= SwapEndian(FStream.ReadQWord);
end;
if TimeScale > 0 then FDuration:= ADuration / TimeScale;
end;
FStream.Seek(0, soBeginning);
if (FDuration > 0) and FindAtomHeader('mdat', @MediaSize) then
begin
FBitRate:= MediaSize * 8 / FDuration / 1000;
end;
end;
procedure TMP4file.ReadSampleDescription;
var
Number: LongWord;
begin
if not FindAtomHeader('moov') then Exit;
if not FindAtomHeader('trak') then Exit;
if not FindAtomHeader('mdia') then Exit;
if not FindAtomHeader('minf') then Exit;
if not FindAtomHeader('stbl') then Exit;
if FindAtomHeader('stsd') then
begin
FStream.Seek(4, soCurrent);
Number:= SwapEndian(FStream.ReadDWord);
if Number = 1 then
begin
if FindAtomHeader('mp4a') then
begin
FStream.Seek(16, soCurrent);
FChannels:= SwapEndian(FStream.ReadWord);
FSampleSize:= SwapEndian(FStream.ReadWord);
FStream.Seek(2, soCurrent);
FSampleRate:= SwapEndian(FStream.ReadDWord);
end;
end;
end;
end;
constructor TMP4file.Create;
begin
end;
destructor TMP4file.Destroy;
begin
inherited Destroy;
end;
function TMP4file.ReadFromFile(const FileName: String): Boolean;
var
AtomSize: Int64;
AtomName: TAtomName;
begin
ResetData;
FStream:= TFileStreamEx.Create(FileName, fmOpenRead or fmShareDenyNone);
try
FFileSize:= FStream.Size;
Result:= LoadAtomHeader(AtomName, AtomSize) and SameText(AtomName, 'ftyp');
if Result then
begin
FStream.Seek(AtomSize, soCurrent);
ReadSampleDescription;
ReadMovieHeader;
end;
finally
FreeAndNil(FStream);
end;
end;
end.

View file

@ -29,7 +29,7 @@ interface
uses
Classes, SysUtils, DCStrUtils, MPEGaudio, Musepack, OggVorbis, ID3v1, ID3v2,
APEtag, FLACfile, Monkey, AACfile, CDAtrack, WMAfile, WAVfile, TTA, TwinVQ,
AC3, DTS, WAVPackfile, OptimFROG;
AC3, DTS, WAVPackfile, OptimFROG, MP4file;
type
@ -42,6 +42,7 @@ type
FTTA: TTTA;
FTwinVQ: TTwinVQ;
FMonkey: TMonkey;
FMP4file: TMP4file;
FAACfile: TAACfile;
FWMAfile: TWMAfile;
FWAVfile: TWAVfile;
@ -69,6 +70,7 @@ type
function ReadTTA: Boolean;
function ReadTwinVQ: Boolean;
function ReadMonkey: Boolean;
function ReadMP4file: Boolean;
function ReadAACfile: Boolean;
function ReadWMAfile: Boolean;
function ReadWAVfile: Boolean;
@ -298,6 +300,19 @@ begin
end;
end;
function TAudioData.ReadMP4file: Boolean;
begin
Result:= FMP4file.ReadFromFile(FFileName) and FMP4file.Valid;
if Result then
begin
SampleRate:= FMP4file.SampleRate;
BitRate:= Round(FMP4file.BitRate);
Duration:= Round(FMP4file.Duration);
DurationHMS:= FormatDuration(Duration);
Channels:= FormatChannels(FMP4file.Channels);
end;
end;
function TAudioData.ReadAACfile: Boolean;
begin
Result:= FAACfile.ReadFromFile(FFileName) and FAACfile.Valid;
@ -484,6 +499,7 @@ begin
FTTA:= TTTA.Create;
FTwinVQ:= TTwinVQ.Create;
FMonkey:= TMonkey.Create;
FMP4file:= TMP4file.Create;
FAACfile:= TAACfile.Create;
FWMAfile:= TWMAfile.Create;
FWAVfile:= TWAVfile.Create;
@ -503,6 +519,7 @@ begin
FTTA.Free;
FTwinVQ.Free;
FMonkey.Free;
FMP4file.Free;
FAACfile.Free;
FWMAfile.Free;
FWAVfile.Free;
@ -585,6 +602,10 @@ begin
begin
Result:= ReadOptimFrog;
end
else if (FileExt = 'm4a') then
begin
Result:= ReadMP4file;
end
else Result:= False;
if Result then