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:
- ošetřili jsme událost OnCreate hlavního formuláře, jejímž významem je především provést potřebná nastavení a inicializace. V této souvislosti bych rád zopakoval věc, kterou jsem zmiňoval již několikrát, která však stále některým čtenářům uniká (zaměřte se především na příspěvek s nadpisem Dekujeme, odejdete). Je samozřejmé, že díky integrovanému prostředí Delphi je mnohem efektivnější a jednodušší nastavit titulky komponent přímo v Object Inspectoru a nesepisovat hromadu příkazů typu Label1.Caption := `nazdar`. Důvod, proč jsem při psaní článků zvolil tento, komplikovanější a méně pohodlný způsob, spočívá v tom, že podle mého přesvědčení je pro čtenáře šikovnější, když si mohou vytvořit ukázkovou aplikaci pouhým zkopírováním zdrojového kódu a nemusí trávit čas nekonečným klepáním do Object Inspectoru. Kromě toho věřím, že uvedení příkazu Label1.Caption := `nazdar` je efektivnější než napsání věty „V Object Inspectoru najděte komponentu Label1, aktivujte vlastnost Caption a do příslušného políčka vyplňte název Nazdar.“
- implementovali jsme metodu, která načte tag ze souboru za pomoci metod třídy Tid3v2tag,
- implementovali jsme metodu, která uloží změněný tag zpět do souboru za pomoci metod téže třídy.
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.
- Zaměříme se tedy na úplný konec uvedeného zdrojového kódu a před závěrečný příkaz end připíšeme několik řádků, které způsobí provedení výše uvedených operací:
- při spuštění aplikace vytvoří objekt myTag třídy Tid3v2,
- při ukončení aplikace uvolní tento objekt z paměti („uklidí po sobě“).
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.