Živě.cz o počítačích a internetu

Umíme to s Delphi: 147. díl – MP3, ID3, ID3v2 – úplné dokončení

Václav Kadlec 3.5.2005

V dnešním článku definitivně dokončíme popis technologií MP3, ID3 a ID3v2. Do zdárného konce přitom dotáhneme vytváření jednoduchého editoru ID3v2 tagů, který jsme začali implementovat před týdnem. V té souvislosti si také vysvětlíme jednu velmi důležitou a široce použitelnou věc týkající se pascalských programových jednotek (unit): sekce Initialization a Finalization.

Dnešní článek definitivně uzavře problematiku souborů MP3 a jejich textových rozšíření – ID3 tagů. Pojďme se nejprve podívat na to, čím jsme se zabývali v posledních několika článcích.

Minulý díl seriálu se týkal použití speciální knihovny (dostupné na URL http://www.audioxl.com/idl-download.html, která umožňuje poměrně jednoduše implementovat funkčnost ID3v2 tagů v Delphi aplikacích. Před týdnem jsme už také začali s tvorbou ukázkové aplikace, zahájili jsme implementaci jednoduchého editoru ID3 tagů, který podporuje druhou verzi tohoto standardu.

Dnes tvorbu aplikace dokončíme. Vzhledem k tomu, že dnešní článek bezprostředně navazuje na informace uvedené před týdnem, poprosil bych případné čtenáře, kterým předchozí díl unikl, aby se na něj nejprve podívali, protože v opačném případě jim možná budou chybět základy potřebné pro pochopení (a vůbec sledování) obsahu dnešního dílu.

Pokračujeme ve vytváření aplikace

Shrňme, kam jsme dosud při tvorbě ukázkové aplikace pokročili:

Dnes nás v zásadě čeká doplnit pouze dvě maličkosti: vytvoření samotného objektu při startu aplikace a jeho následné uvolnění při ukončení.

Vzhledem k tomu, že tyto činnosti nejsou příliš náročné, zbývá odpovědět v podstatě jen jednu ukázku: kam přesně vytvoření objektu zařadit. Autoři knihovny zvolili při implementace svého demopříkladu sekce Initialization a Finalization. My se jejich volby přidržíme a význam obou sekcí si vysvětlíme.

Nejprve se tedy pojďme podívat, jak bude vypadat implementace. Níže uvedený výpis ukazuje zdrojový kód modulu tak, jak jsme jej vytvořili před týdnem:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, id3v2, StdCtrls;

type
  TForm1 = class(TForm)
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    Edit4: TEdit;
    Edit5: TEdit;
    Edit6: TEdit;
    Edit7: TEdit;
    Button1: TButton;
    Button2: TButton;
    Label6: TLabel;
    OpenDialog1: TOpenDialog;
    Label7: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  myTag: TID3v2tag;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  Label1.Caption := `Nazev`;
  Label2.Caption := `Interpret`;
  Label3.Caption := `Album`;
  Label4.Caption := `Rok`;
  Label5.Caption := `Zanr`;
  Label6.Caption := `Komentar`;
  Label7.Caption := `Cislo skladby`;

  Button1.Caption := `Nacist tag`;
  Button2.Caption := `Ulozit tag`;

  Edit1.Text := ``;
  Edit2.Text := ``;
  Edit3.Text := ``;
  Edit4.Text := ``;
  Edit5.Text := ``;
  Edit6.Text := ``;
  Edit7.Text := ``;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  tempStr : string;
  tempInt : word;
  myCOMM : COMM;

begin
  if OpenDialog1.execute then begin
    tempInt := myTag.loadFromFile(OpenDialog1.filename, 0);
    if (tempInt > 255) then
      ShowMessage(format(`Error #%d! Could not load Tag!`,[tempInt]))
    else begin
      myTag.getAsciiText(`TIT2`, tempStr); //Get Song Title
      Edit1.text := tempStr;

      myTag.getAsciiText(`TPE1`, tempStr); //Get Artist Name
      Edit2.text := tempStr;

      myTag.getAsciiText(`TALB`, tempStr); //Get Album Name
      Edit3.text := tempStr;

      myTag.getAsciiText(`TYER`, tempStr); //Get Release Year
      Edit4.text := tempStr;

      myTag.getAsciiText(`TCON`, tempStr); //Get Genre
      Edit5.text := tempStr;

      myTag.getAsciiText(`TRCK`, tempStr); //Get Track #
      Edit7.text := tempStr;

      myTag.getCOMM(myCOMM, ``); //Get basic comment (no description)
      Edit6.text := myCOMM.body;
    end;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  tempInt : word;
  myCOMM : COMM;

begin
  myTag.setAsciiText(`TIT2`, Edit1.text); //Set Song Title
  myTag.setAsciiText(`TPE1`, Edit2.text); //Set Artist Name
  myTag.setAsciiText(`TALB`, Edit3.text); //Set Album Name
  myTag.setAsciiText(`TYER`, Edit4.text); //Set Release Year
  myTag.setAsciiText(`TCON`, Edit5.text); //Set Genre
  myTag.setAsciiText(`TRCK`, Edit7.text); //Set Track #

  myCOMM.encoding := etASCII;
  myCOMM.language := `ENG`;
  myCOMM.description := ``;
  myCOMM.body := Edit6.text;
  myTag.setCOMM(myCOMM, myCOMM.description);
  tempInt := myTag.saveToFile;
  if (tempInt > 255) then
    showMessage(format(`Error #%d! Could not save Tag!`,[tempInt]));
end;

end.

Při popisu použité knihovny jsme si řekli důležitou věc: knihovna zapouzdřuje celý ID3v2 tag prostřednictvím třídy TID3v2. Musíme tedy vytvořit objekt této třídy, s nímž budeme po celou dobu běhu aplikace pracovat. Zdůrazněme, že definování proměnné myTag typu Tid3v2 ještě neznamená samotné vytvoření celého dynamického objektu. Definováním proměnné myTag jsme ve své podstatě pouze vytvořili název pro objekt, ale potřebujeme-li s objektem skutečně pracovat, musíme mu vyhradit místo v operační paměti. To se provede zavoláním konstruktoru Create.

initialization
myTag := Tid3v2tag.create;

finalization
if myTag <> nil then
 myTag.free;

Samotné vytvoření objektu prostřednictvím konstruktoru Create není obtížné, totéž platí o jeho konečném uvolnění z paměti prostřednictvím metody free (za zmínku stojí snad jen test na nulový ukazatel – pokud by objekt při uvolňování neexistoval, ukazoval by ukazatel myTag na nil, tedy nikam. Pak bychom žádné uvolňování neprováděli, protože by v podstatě nebylo co uvolňovat).

Pojďme se spíše zaměřit na klíčová slova initialization a finalization. Jaký je jejich význam? Proč jsme je použili?

Sekce Initialization a Finalization

Přítomnost sekcí Initialization a Finalization je nepovinná (však jsme je dosud v našich aplikacích nepoužívali). Initialization a Finalization sekce přitom nejsou žádnou novou myšlenkou nebo snad objevem Delphi: jedná se o standardní programové prostředky používané v objektovém Pascalu. Obě se používají v programových jednotkách (Units), nikoliv v hlavním programu: tam jednak nemají žádný význam a koneckonců by nebyla ani kam umístit.

Proč se tyto dvě sekce používají? Podívejme se na rozdíl mezi hlavním programem v Pascalu (tedy na kód uzavřený mezi klíčová slova begin a end) a programovou jednotkou (Unit). Zatímco v hlavním programu platí, že po spuštění aplikace se začnou postupně provádět jednotlivé příkazy uvedené za begin, u programové jednotky nic takového neplatí. Jinak řečeno, programová jednotka nemá definovaný žádný bod, v němž by bylo zaručeno zahájení jejího provádění. Jednotlivé sekce programové jednotky jsou vykonávány podle toho, jak jsou odkudsi volány, v dopředu neznámém pořadí.

Co si ale počít v případě, že programová jednotka vyžaduje ke svému běhu vykonání určité úvodní akce jako například inicializace proměnných nebo vytvoření dynamických objektů, které jsou pro běh jednotky nutné? Totéž platí pro konec jednotky: co když jednotka při svém běhu vytvoří jakýkoliv nepořádek, který si po sobě potřebuje „uklidit“ při skončení svého běhu?

Vzhledem k tomu, co jsme si o programových jednotkách právě řekli (neexistuje žádný bod, jehož provedení při startu nebo konci jednotky bychom měli zaručeno), by provedení akce na začátku nebo konci běhu jednotky jednoduše nebylo možné zaručit.

To by byl velký problém, a právě proto jsou tu sekce Inicialization, resp. Finalization. Jak asi už tušíte, kód uvedený v těchto dvou sekcích se provede na úplném začátku, resp. na konci jednotky. Tyto dvě sekce jsou tedy zmíněnými dvěma body, jejichž provedení na začátku, resp. konci je zaručeno.

Podívejme se podrobněji na sekci Initialization. Ta začíná uvedením příslušného klíčového slova a pokračuje tak dlouho, dokud nenapíšeme klíčové slovo Finalization (tedy dokud nezahájíme sekci Finalization) a nebo (v případě, že žádná sekce Finalization neexistuje) do konce jednotky. Sekce Initialization obsahuje příkazy, které jsou provedeny postupně jeden po druhém při spuštění programu využívajícího danou jednotku. Typické využití sekce Initialization proto spočívá ve vytvoření a inicializaci datových struktur apod.

Dodejme snad jen to, že používá-li program víc jednotek, sekce Initialization jednotlivých jednotek jsou spouštěny v pořadí, v jakém jsou názvy jednotek uvedeny v sekci Uses programu.

Sekce Finalization je také nepovinná, může se však objevit pouze v případě, že jednotka obsahuje také sekci Initialization. Sekce začíná uvedením klíčového slova Finalization a pokračuje až do konce jednotky (nemá tedy žádný svůj vlastní end). Má přitom zcela analogický význam jako sekce Initialization: obsahuje příkazy, které mají být vykonány těsně před ukončením aplikace. Existuje zde jedna výjimka: je-li program ukončen použitím příkazu Halt (obvykle při vzniku vážných problémů, na které neumíme jinak reagovat), není obsah sekce vykonán.

Sekce se typicky používá pro uvolnění zdrojů (paměťových objektů apod.), které byly alokovány v sekci Initialization.

Používá-li aplikace více jednotek, jsou jejich sekce Finalization vykonávány v opačném pořadí, než v jakém byly vykonány sekce Initialization.

Dodejme ještě jednu věc: s výjimkou příkazu Halt je sekce Finalization při ukončení programu provedena vždycky, i v případě vzniku běhové chyby (runtime error). Znamená to, že sekce by si měla poradit i s nekorektně inicializovanými daty: nemůžeme si totiž být jisti, že sekce Initialization korektně proběhla. To je mimo jiné důvodem, proč ve výše uvedeném zdrojovém kódu testujeme uvnitř sekce Finalization ukazatel myTag na hodnotu nil.

Chcete-li si význam sekcí vyzkoušet v praxi, vytvořte v Delphi jednoduchou aplikaci, která nebude nic dělat a do jejíž jednotky Unit1 připíšete pouze čtyři řádky před závěrečný end:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs;

type
  TForm1 = class(TForm)
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

initialization
  ShowMessage(`Initialization`);

finalization
  ShowMessage(`Finalization`);

end.

Pokud nyní aplikaci spustíte, dojde při jejím spuštění (ještě před zobrazením hlavního formuláře) k vypsání zprávy „Initialization“. Při jejím ukončení se nevypíše nic (snad jedině pokud máte velmi bystré oko, všimnete si určitého probliknutí): uhádnete, proč?

Zpět k editoru tagů

Tolik k popisu klíčových slov Initialization a Finalization. Nyní tedy kompletně rozumíme zdrojovému kódu našeho Editoru. Můžeme jej přeložit, spustit a pokochat se pohledem na krásnou aplikaci umožňující načítat, editovat a ukládat ID3 tagy i ve verzi 2.

Zdrojový kód

Na úplný závěr uvedeme kompletní zdrojový kód aplikace:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, id3v2, StdCtrls;

type
  TForm1 = class(TForm)
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Edit1: TEdit;
    Edit2: TEdit;
    Edit3: TEdit;
    Edit4: TEdit;
    Edit5: TEdit;
    Edit6: TEdit;
    Edit7: TEdit;
    Button1: TButton;
    Button2: TButton;
    Label6: TLabel;
    OpenDialog1: TOpenDialog;
    Label7: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  myTag: TID3v2tag;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  Label1.Caption := `Nazev`;
  Label2.Caption := `Interpret`;
  Label3.Caption := `Album`;
  Label4.Caption := `Rok`;
  Label5.Caption := `Zanr`;
  Label6.Caption := `Komentar`;
  Label7.Caption := `Cislo skladby`;

  Button1.Caption := `Nacist tag`;
  Button2.Caption := `Ulozit tag`;

  Edit1.Text := ``;
  Edit2.Text := ``;
  Edit3.Text := ``;
  Edit4.Text := ``;
  Edit5.Text := ``;
  Edit6.Text := ``;
  Edit7.Text := ``;

  Caption := `Editor ID3 tagu`;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  tempStr : string;
  tempInt : word;
  myCOMM : COMM;

begin
  if OpenDialog1.execute then begin
    tempInt := myTag.loadFromFile(OpenDialog1.filename, 0);
    if (tempInt > 255) then
      ShowMessage(format(`Error #%d! Could not load Tag!`,[tempInt]))
    else begin
      myTag.getAsciiText(`TIT2`, tempStr); //Get Song Title
      Edit1.text := tempStr;

      myTag.getAsciiText(`TPE1`, tempStr); //Get Artist Name
      Edit2.text := tempStr;

      myTag.getAsciiText(`TALB`, tempStr); //Get Album Name
      Edit3.text := tempStr;

      myTag.getAsciiText(`TYER`, tempStr); //Get Release Year
      Edit4.text := tempStr;

      myTag.getAsciiText(`TCON`, tempStr); //Get Genre
      Edit5.text := tempStr;

      myTag.getAsciiText(`TRCK`, tempStr); //Get Track #
      Edit7.text := tempStr;

      myTag.getCOMM(myCOMM, ``); //Get basic comment (no description)
      Edit6.text := myCOMM.body;
    end;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  tempInt : word;
  myCOMM : COMM;

begin
  myTag.setAsciiText(`TIT2`, Edit1.text); //Set Song Title
  myTag.setAsciiText(`TPE1`, Edit2.text); //Set Artist Name
  myTag.setAsciiText(`TALB`, Edit3.text); //Set Album Name
  myTag.setAsciiText(`TYER`, Edit4.text); //Set Release Year
  myTag.setAsciiText(`TCON`, Edit5.text); //Set Genre
  myTag.setAsciiText(`TRCK`, Edit7.text); //Set Track #

  myCOMM.encoding := etASCII;
  myCOMM.language := `ENG`;
  myCOMM.description := ``;
  myCOMM.body := Edit6.text;
  myTag.setCOMM(myCOMM, myCOMM.description);
  tempInt := myTag.saveToFile;
  if (tempInt > 255) then
    showMessage(format(`Error #%d! Could not save Tag!`,[tempInt]));
end;

///////// vytvoreni objektu pri startu
initialization
myTag := Tid3v2tag.create;

///////// uvolneni objektu pri ukonceni
finalization
if myTag <> nil then
 myTag.free;

end.

Na závěr

Tímto okamžikem končíme sérii článků věnovanou technologiím MP3, ID3 a ID3v2. Nakonec jsme se jí věnovali o mnoho déle, než bylo původně v plánu, na druhou stranu věřím, že jste se i tak dozvěděli něco nového a zajímavého.

Za týden se nicméně vrhneme na zbrusu nové téma. Věřím, že bude pro vás atraktivní. Podrobnosti prozatím neprozradím: nechte se překvapit.