查看單個文章
舊 2006-02-24, 09:18 AM   #3 (permalink)
psac
榮譽會員
 
psac 的頭像
榮譽勳章
UID - 3662
在線等級: 級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時
註冊日期: 2002-12-07
住址: 木柵市立動物園
文章: 17381
現金: 5253 金幣
資產: 33853 金幣
預設

趣談 MP3 和 Perl
MP3 標籤的簡單介紹
首先有音樂。然後出現電腦。電腦速度很慢且只能發出蜂鳴聲。即使使用諸如 PC 揚聲器這種很令人悲傷的工具(噢,我真希望成為 Apple 和 Amiga 用戶),我們也編寫程序產生音樂,在遊戲和娛樂中使用。之後,音效卡越來越好,辦公室裡到處發出環繞音響和 THX 認證的揚聲器所發出的震耳欲聾的聲音。

在硬體發展的同時,也產生了很多聲音格式。.mid 適用於 MIDI 音調、.voc、.mod、.wav 等。專有的 MP3 格式(涉及到德國 Fraunhofer 學院擁有的很多專利)隨著時間的推移流行開來 —— 它提供很好的壓縮和效能。除 MP3 外還有許多格式,著名的有 Ogg Vorbis,但當今 MP3 似乎仍是音樂儲存於格式的最佳選項。

MP3 文件的一個優點是可以使用 ID3 標籤來加注標籤。文件內部是有關它的訊息 —— 通常稱為元資料。唱片集、藝術家、曲目名稱、註釋(使用 ID3 版本 1.1)甚至曲目數量都可以儲存於在 ID3 標籤中,只要不超過特定字元數限制即可。

ID3 版本 1.1 的後續版本是 ID3 版本 2(簡稱為 ID3v2),除簡單性外,後者幾乎在所有方面都超過了前者。ID3v2 可以處理多種語言,在每個標籤元素中儲存於任意長的資料,甚至將圖片儲存於為標籤的一部分。但不幸的是,使用 ID3v2 要瞭解到 TALB 是唱片集的名稱,TIT2 是曲目數量。Ogg Vorbis 格式要花費很長時間才能識別,其中藝術家元素被稱為...等等它吧...ARTIST!(公平地說,這僅僅是一個慣例 —— Ogg Vorbis 註釋是無格式的)。不幸的是,現存的數十億首 MP3 文件都不能在不損失品質的情況下轉換為 Ogg Vorbis 格式或任何其他格式,因此,至少在接下來的 5 年裡,您可能發現,我們不僅會使用下個「熱門」格式,還要使用 MP3 文件。

我已非常努力地從實際的 ID3 標籤中抽取標籤作為內容。當時機來臨時,修改 autotag.pl 會很容易,因此除 ID3 外,它還處理其他加注標籤的格式。


一位自我陶醉的電腦和音樂愛好者都需要能夠操縱 MP3 —— 娛樂性數位音樂的事實標準。在本文中,Ted 介紹了幾種使用autotag.pl 應用程式管理和操縱(搜尋、標記、重新命名和註釋,等等)MP3 的方法。Ted 向讀者詳細介紹了此應用程式,描述了 CPAN模組如何啟用該應用程式。
對於現在瞭解電腦的音樂愛好者而言,操縱 MP3 文件是一項必須具備的技能。雖然其他音樂文件格式已存在並在蓬勃發展著,但本文還是主要討論 MP3 格式,因為眾所周知,它是當今最流行的格式。但是,本文所講述的一般方法也可用於處理其他允許使用標籤(tag)的音樂文件格式。實際上,很多使用標籤的文件格式都可以從類似本文中的 autotag.pl 程序中受益。歡迎您提出建議。

本文將一般性地討論有關 Perl 的問題 ,特別關注 MP3 文件的操縱,並詳細介紹了 autotag.pl 應用程式。

儘管已經有了 MP3::Info 、 MP3::ID3Lib 、 MusicBrainz::Client 和 AudioFile::Identify::MusicBrainz 模組,而且這些模組可能很有用,但我只使用 MP3::ID3Lib 的主要理由是因為它需要 id31ib 軟體(請參閱 參考資料)。雖然 MP3::Info 是純 Perl 語言編寫的而且安裝也很簡單,但我發現 MP3::Tag 功能更強大。之所以沒有使用 MusicBrainz::Client 和 AudioFile::Identify::MusicBrainz ,是因為 MusicBrainz 似乎是比 FreeDB 更不全面的已發行 CD 的資料庫。在本文的結尾,將向讀者介紹 ID3 標籤加注模組和曲目訊息模組的選項。我經過試驗和失敗而艱難獲得的經驗表明, MP3::Tag 和 WebService::FreeDB 是最佳的模組。

雖然 CDDB (Gracenote) 磁牒庫非常全面,但我還是沒有選項使用它。Gracenote 是一家擁有 CD 曲目列表的專有資料庫(只允許對資料庫執行搜尋,不能大量下載)的公司。在 Gracenote 只擁有 CDDB 的早期,志願者貢獻了這些資料庫的相當一部分內容。而 FreeDB 是一個志願者經過有組織的努力提供的免費、無限制的 CD 曲目資料庫。FreeDB 資料庫的整個內容都可以下載,無版權限制 —— 因此,如果您願意,可以建立自己的 FreeDB 伺服器。

我不使用的模組並不是因為這些模組一定不好,因此,如果您喜歡,您可以使用它們。關於個人經驗和上述原因,我只是更喜歡 MP3::Tag 和 WebService::FreeDB 。實際的讀寫標籤在函數中進行了抽像,因此,如果使用不同的模組讀寫 MP3 標籤,就不需要更改很多內容。

我還應提一下,在 Linux 內部的 xterm 和 Eterm 終端模擬器中, Term::ReadLine::Gnu 模組比預設模組 Term::ReadLine::Perl 能更好地工作。如果您注意到在提示輸入期望的文本時出現一些奇怪的行為,那麼可能要將其安裝在 Term::ReadLine 之上。
基本的 autotag.pl 函数
我把 autotag.pl 几个功能放在了不同的函数中。首先, contains_word_char() 是一个判断某些文本中是否包含某个词(在 Perl 中是 \w in Perl)中的字符的函数。该函数也会正确地处理未定义的值,尽管在警告打开时,常规表达式在匹配未定义的值时会输出警告信息。该函数是极为有用的,因为它不显示警告信息;为了不使用函数而又达到这个目的,您必须检查是否每次都定义了字符串。
清單 1. contains_word_char() 函數

# {{{ contains_word_char: return 1 if the text contains a word character
sub contains_word_char
{
my $text = shift @_;
return $text && length $text && $text =~ m/\w/;
}
# }}}



接下來是輸入例程。這些程序相當長,它們試圖處理程序所需要的用戶交互的大多數情況。

清單 2. get_tag() 函數


# {{{ get_tag: get a ID3 V2 tag, using V1 if necessary
sub get_tag
{
my $file = shift @_;
my $upgrade = shift @_;
my $mp3 = MP3::Tag->new($file);

return undef unless defined $mp3;

$mp3->get_tags();

my $tag = {};

if (exists $mp3->{ID3v2})
{
my $id3v2 = $mp3->{ID3v2};
my $frames = $id3v2->supported_frames();
while (my ($fname, $longname) = each %$frames)
{
# only grab the frames we know
next unless exists $supported_frames{$fname};

$tag->{$fname} = $id3v2->get_frame($fname);
delete $tag->{$fname} unless defined $tag->{$fname};
$tag->{$fname} = $tag->{$fname}->{Text} if $fname eq 'COMM';
$tag->{$fname} = $tag->{$fname}->{URL} if $fname eq 'WXXX';
$tag->{$fname} = '' unless defined $tag->{$fname};
}
}
elsif (exists $mp3->{ID3v1})
{
warn "No ID3 v2 TAG info in $file, using the v1 tag";
my $id3v1 = $mp3->{ID3v1};
$tag->{COMM} = $id3v1->comment();
$tag->{TIT2} = $id3v1->song();
$tag->{TPE1} = $id3v1->artist();
$tag->{TALB} = $id3v1->album();
$tag->{TYER} = $id3v1->year();
$tag->{TRCK} = $id3v1->track();
$tag->{TIT1} = $id3v1->genre();

if ($upgrade && read_yes_no("Upgrade ID3v1 tag to ID3v2 for $file?", 1))
{
set_tag($file, $tag);
}
}
else
{
warn "No ID3 TAG info in $file, creating it";
$tag = {
TIT2 => '',
TPE1 => '',
TALB => '',
TYER => 9999,
COMM => '',
};
}
print "Got tag ", Dumper $tag
if $config->DEBUG();
return $tag;
}
# }}}



惟一一個稍微與眾不同的函數是 read_yes_no() ,可以給它一個 Y 或 1 的預設參數來使預設值為真,任何其他的參數都會使預設值為假。這樣,當用戶按下Enter鍵鍵或者空格鍵時,我可以讓 read_yes_no() 函數接受不同的預設值。另外,Backspace 鍵或 Delete 鍵將使預設值反轉。這段程式碼不華麗,但很實用。

autotag.pl 的開頭部分
應用程式 autotag.pl 以一些啟始化例程開始。

清單 3. 啟始化

use constant SEARCH_ALL => 'all';

my %freedb_searches = (
artist => { keywords => [], abbrev => 'I', tagequiv => 'TPE1' },
title => { keywords => [], abbrev => 'T', tagequiv => 'TALB' },
track => { keywords => [], abbrev => 'K', tagequiv => 'TIT2' },
rest => { keywords => [], abbrev => 'R', tagequiv => 'COMM' },
);

# maps ID3 v2 tag info to WebService::FreeDB info
my %info2freedb = (
TALB => 'cdname',
TPE1 => 'artist',
);

my %supported_frames = (
TIT1 => 1,
TIT2 => 1,
TRCK => 1,
TALB => 1,
TPE1 => 1,
COMM => 1,
WXXX => 1,
TYER => 1,
);

my @supported_frames = keys %supported_frames;

my $term = new Term::ReadLine 'Input> '; # global input



EARCH_ALL 是一個常數,當用戶想在任何地方搜尋一個詞的時候,比如曲目名、藝術家名等,就會使用它。為了防止有人想把它改為另外某個值,我把它設定為常數,但它也可能已經被硬編碼為「all」。

%freedb_searches 散列將 FreeDB 字段映射到有關它們的訊息上,包括 ID3v2 標籤元素。例如,它說明 FreeDB 怎樣稱呼那些在 MP3 標籤中被稱為「TPE1」的「artist」。在該散列 列項中的「abbrev」字段被用來定義指令行開關,這樣,隨後我可以關於 %freedb_searches 訊息定義一個 -artist 開關,它可以被簡寫為 -i 。

%info2freedb 散列將光碟中的所有曲目的 FreeDB 字段都映射到 ID3v2 字段。它們不是 %freedb_searches 中的字段,這是一種不同的映射,它表明,對於一個光碟集的所有曲目而言,「cdname」和 「artists」(也分別被稱為「TALB」和「TPE1」)是相同的。

我將用 %supported_frames 散列和 @supported_frames 列表來表示我支持哪些 ID3v2 標籤元素。我是從該列表產生了這個散列,而不是從該散列中獲得這個列表(解釋兩者之間的差別離題太遠,所以不再贅述)。大規模加注標籤時,以及在編寫 ID3v2 標籤時,都要用到已獲支持的框架(我只是修改已獲支持的框架而已)。

最後,為了讓用戶在整個應用程式中輸入資料,我新增了一個 Term::ReadLine 對象。

下面,我啟始化 AppConfig 選項,這樣做雖然加重了我的負擔,但是有益的。

清單 4. AppConfig 的啟始化

# {{{ set up AppConfig and process -help

my $config = AppConfig->new();

$config->define(
DEBUG =>
{ ARGCOUNT => ARGCOUNT_ONE, DEFAULT => 0, ALIAS => 'D' },

CONFIG_FILE =>
{ ARGCOUNT => ARGCOUNT_ONE, DEFAULT => 0, ALIAS => 'F' },

HELP =>
{ ARGCOUNT => ARGCOUNT_NONE, DEFAULT => 0, ALIAS => 'H' },

DUMP =>
{ ARGCOUNT => ARGCOUNT_NONE, DEFAULT => 0 },

ACCEPT_ALL =>
{ ARGCOUNT => ARGCOUNT_NONE, DEFAULT => 0, ALIAS => 'C' },

DRYRUN =>
{ ARGCOUNT => ARGCOUNT_NONE, DEFAULT => 0, ALIAS => 'N' },

GUESS_TRACK_NUMBERS_ONLY =>
{ ARGCOUNT => ARGCOUNT_NONE, DEFAULT => 0, ALIAS => 'G' },

STRIP_COMMENT_ONLY =>
{ ARGCOUNT => ARGCOUNT_NONE, DEFAULT => 0, ALIAS => 'SC' },

MASS_TAG_ONLY =>
{ ARGCOUNT => ARGCOUNT_HASH, ALIAS => 'M' },

RENAME_ONLY =>
{ ARGCOUNT => ARGCOUNT_NONE, DEFAULT => 0, ALIAS => 'RO' },

RENAME_MAX_CHARS =>
{ ARGCOUNT => ARGCOUNT_ONE, DEFAULT => 30},

RENAME_FORMAT =>
{ ARGCOUNT => ARGCOUNT_ONE, DEFAULT => '%a-%t-%n-%c-%s.mp3'},

RENAME_BADCHARS =>
{ ARGCOUNT => ARGCOUNT_LIST, ALIAS => 'RB' },

RENAME_REPLACECHARS =>
{ ARGCOUNT => ARGCOUNT_LIST, ALIAS => 'RR' },

RENAME_REPLACEMENT =>
{ ARGCOUNT => ARGCOUNT_ONE, DEFAULT => '_' },

FREEDB_HOST =>
{ ARGCOUNT => ARGCOUNT_ONE, DEFAULT => 'http://www.freedb.org', },

OR =>
{ ARGCOUNT => ARGCOUNT_NONE, DEFAULT => '0', },

SEARCH_ALL() =>
{ ARGCOUNT => ARGCOUNT_LIST, ALIAS => 'A' },
);

foreach my $search (keys %freedb_searches)
{
$config->define($search => {
ARGCOUNT => ARGCOUNT_LIST,
ALIAS => $freedb_searches{$search}->{abbrev},
});
}
$config->args();

$config->file($config->CONFIG_FILE())
if $config->CONFIG_FILE();

unless (scalar @{$config->RENAME_BADCHARS()})
{
push @{$config->RENAME_BADCHARS()}, split(//, "\"`!'?&[]()/;\n\t");
}

unless (scalar @{$config->RENAME_REPLACECHARS()})
{
push @{$config->RENAME_REPLACECHARS()}, split(//, " ");
}

if ($config->HELP())
{
print <<EOHIPPUS;
$0 [options] File1.mp3 File2.mp3 ...

Options:
-help (-h) : print this help
-config_file (-f) N : use this config file, see AppConfig module docs for format
-debug (-d) N : print debugging information (level N, 0 is lowest)
-dump : just dump the list of albums and tracks within them
-dryrun (-n) : do everything but modify the MP3 files
-freedb_host H : set the FreeDB host, default "www.freedb.org"
-or : search for keyword A or keyword B, not A and B as usual

-accept_all (c) : accept all search results for consideration for each file,
also accept all renames without asking

-rename_badchars (-rb) A -rb B : characters A and B to remove when renaming

-rename_replacechars (-rr) A -rr B : characters A and B to replace
when renaming

-rename_maxchars N : use at most this many characters from a tag
element when renaming, default: ${\$config->RENAME_MAX_CHARS()}

-rename_replacement X : character to use when replacing,
default: [${\$config->RENAME_REPLACEMENT()}]

-rename_format (-f) F : format for renaming; default "${\$config->RENAME_FORMAT()}"
%a -> Artist
%t -> Track number
%n -> Album name
%c -> Comment
%s -> Song title

-guess_track_numbers_only (-g) : guess track numbers using the file
name, then exit

-rename_only (-ro) : rename tracks using the given format (see
-rename_format), then exit

-mass_tag_only (-m) A=X -m B=Y : mass-tag files (tag element A is X,
B is Y), then exit (tag elements
available: @supported_frames)

-strip_comment_only (-sc) : strip comments and URLs, then exit

Repeatable options (you can specify them more than once, K is the keyword):

-all (-a) K : search everywhere
-artist (-i) K : search for these artists
-title (-t) K : search for these titles
-track (-k) K : search for these tracks
-rest (-r) K : search for these keywords everywhere else

Note that the repeatable options are cumulative, so artist A and title
B will produce matches for A and B, not A or B. In the same way,
artist A and artist B will produce matches for A and B, not A or B.
If you want to match A or B terms, use -or, for instance:

$0 -or -artist "pink floyd" -artist "fred flintstone"

EOHIPPUS

exit;
}

# }}}



是的,所有這些程式碼都是啟始化指令行選項的。通過使用 AppConfig ,可以在整個程序中使用和修改這些選項。使用 AppConfig 還有很多好處,不過這些內容超出了本文的範圍(有關 AppConfig 的更多訊息,請參閱 參考資料)。

另外,我還使用 %freedb_searches 散列中的 列項來新增合適的組態選項,這樣可以使用戶和程序員更輕鬆一些。

在載入組態文件以後,如果用戶指定了它,那麼就用有意義的預設值來植入字元置換陣列和不良字元陣列。

最後,處理 -help 開關。注意,通過變數插入不同選項的預設值是如何在說明 文本內列印出來大的。這樣就形成了可讀性非常強的說明 訊息。我總是在增加新的特性之後(但有時候是在之前)立即更新說明 訊息。我認為,說明 文本應該和程序的功能同步,否則人們將不理解程序,也不知道說明 文本說了些什麼。autotag.pl 程序特別需要更多的我的文件說明,POD 風格的我的文件應該比較合適。在您閱讀本文時,這樣的我的文件可能已經有了。POD 我的文件是指令碼的一部分,因此下載的 autotag.pl(請參閱 參考資料)將包括 POD 我的文件(如果我已經將它寫入的話)。

與 ID3v2 標籤相關的函數
get_tag() 函數是 autotag.pl 的基本函數。指出一個 MP3 檔案名,它就會根據該檔案構建一個散列標籤。如果標籤只是 ID3v1 標籤, get_tag() 函數將會免費把它昇級為 ID3 標籤(多麼好的交易!)。如果沒有 ID3 標籤, get_tag() 函數將新增一個。而且, get_tag() 知道分別檢視 COMM 和 WXXX 元素的 Text 和 URL 子元素。

清單 5. get_tag() 函數

# {{{ get_tag: get a ID3 V2 tag, using V1 if necessary
sub get_tag
{
my $file = shift @_;
my $upgrade = shift @_;
my $mp3 = MP3::Tag->new($file);

return undef unless defined $mp3;

$mp3->get_tags();

my $tag = {};

if (exists $mp3->{ID3v2})
{
my $id3v2 = $mp3->{ID3v2};
my $frames = $id3v2->supported_frames();
while (my ($fname, $longname) = each %$frames)
{
# only grab the frames we know
next unless exists $supported_frames{$fname};

$tag->{$fname} = $id3v2->get_frame($fname);
delete $tag->{$fname} unless defined $tag->{$fname};
$tag->{$fname} = $tag->{$fname}->{Text} if $fname eq 'COMM';
$tag->{$fname} = $tag->{$fname}->{URL} if $fname eq 'WXXX';
$tag->{$fname} = '' unless defined $tag->{$fname};
}
}
elsif (exists $mp3->{ID3v1})
{
warn "No ID3 v2 TAG info in $file, using the v1 tag";
my $id3v1 = $mp3->{ID3v1};
$tag->{COMM} = $id3v1->comment();
$tag->{TIT2} = $id3v1->song();
$tag->{TPE1} = $id3v1->artist();
$tag->{TALB} = $id3v1->album();
$tag->{TYER} = $id3v1->year();
$tag->{TRCK} = $id3v1->track();
$tag->{TIT1} = $id3v1->genre();

if ($upgrade && read_yes_no("Upgrade ID3v1 tag to ID3v2 for $file?", 1))
{
set_tag($file, $tag);
}
}
else
{
warn "No ID3 TAG info in $file, creating it";
$tag = {
TIT2 => '',
TPE1 => '',
TALB => '',
TYER => 9999,
COMM => '',
};
}
print "Got tag ", Dumper $tag
if $config->DEBUG();
return $tag;
}
# }}}



set_tag() 函數是 get_tag() 函數的兄弟。它編寫 ID3v2 標籤,檢視 COMM 和 WXXX 框架的子元素。它接受散列引用,比如 get_tag() 函數可能產生的那些散列引用。

清單 6. set_tag() 函數

# {{{ set_tag: set a ID3 V2 tag on a file
sub set_tag
{
my $file = shift @_;
my $tag = shift @_;
my $mp3 = MP3::Tag->new($file);
print Dumper $tag;
my $tags = $mp3->get_tags();
my $id3v2;

if (ref $tags eq 'HASH' && exists $tags->{ID3v2})
{
$id3v2 = $tags->{ID3v2};
}
else
{
$id3v2 = $mp3->new_tag("ID3v2");
}

my %old_frames = %{$id3v2->get_frame_ids()};

foreach my $fname (keys %$tag)
{
$id3v2->remove_frame($fname)
if exists $old_frames{$fname};

if ($fname eq 'WXXX')
{
$id3v2->add_frame('WXXX', 'ENG', 'FreeDB URL', $tag->{WXXX}) ;
}
elsif ($fname eq 'COMM')
{
$id3v2->add_frame('COMM', 'ENG', 'Comment', $tag->{COMM}) ;
}
else
{
$id3v2->add_frame($fname, $tag->{$fname});
}
}

$id3v2->write_tag();
return 0;
}
# }}}



print_tag_info() 函數簡單地列印輸出標籤的摘要。不像我在 autotag.pl 程序中的其他地方使用的 Data:umper 函數(必須指出,有時沒有必要使用), print_tag_info() 函數可以提供漂亮的、面向用戶的散列標籤元素的列印輸出。注意,該函數接受散列引用,而不是實際的檔案名。

指出檔案名和某些可能的 ID3 標籤訊息, guess_track_number() 函數和 guess_artist_and_track() 函數會盡力工作。注意, guess_track_number() 函數知道曲目的數量很少大於 30。

清單 7. print_tag_info()、 guess_track_number()、和 guess_artist_and_track() 函數

# {{{ print_tag_info: print the tag info

sub print_tag_info
{
my $filename = shift @_;
my $tag = shift @_;
my $extra = shift @_ || 'Track info';

# argument checking
return unless ref $tag eq 'HASH';

print "$extra for '$filename':\n";

foreach (keys %$tag)
{
printf "%10s : %s\n", $_, $tag->{$_};
}
}

# }}}

# {{{ guess_track_number: guess track number from ID3 tag and file name
sub guess_track_number
{
my $filename = shift @_;
my $tag = shift @_ || return undef;

$filename = basename($filename); # directories can contain confusing data

# first try to guess the track number from the old tag
if (exists $tag->{TRCK} && contains_word_char($tag->{TRCK}))
{
my $n = $tag->{TRCK} + 0; # fix tracks like 1/10
return $n;
}
elsif ($filename =~ m/([012]?\d).*\.[^.]+$/)
# now look for numbers in the filename (0 through 29)
{
print "Guessed track number $1 from filename '$filename'\n"
if $config->DEBUG();
return $1;
}

return undef; # if all else fails, return undef
}
# }}}

# {{{ guess_artist_and_track: guess artist and track from file name
sub guess_artist_and_track
{
my $filename = shift @_;
my $artist;
my $track;

$filename = basename($filename); # directories can contain confusing data

if ($filename =~ m/([^-_]{3,})\s*-\s*(.{3,})\s*\.[^.]+$/)
{
print "Guessed artist $1 from filename '$filename'\n"
if $config->DEBUG();
$artist = $1;
$track = $2;
}

return ($artist, $track);
}
# }}}



我使用從 FreeDB 搜尋中返回的資料來產生帶有適當元素的匿名散列。雖然 WebService::FreeDB 字段和 ID3v2 標籤元素之間的映射是試驗性的,但它工作得很好。

清單 8. make_tag_from_freedb() 函數

# {{{ make_tag_from_freedb: make the ID3 tag info from a FreeDB entry
sub make_tag_from_freedb
{
my $disc = shift @_;
my $track = shift @_;

# argument checking
return undef unless $track =~ m/^\d+$/;

# note that the user inputs track "1" but WebService::FreeDB gives us that
# track at position 0, so we decrement $track
$track--;

return undef unless exists $disc->{trackinfo};

return undef unless exists $disc->{trackinfo}->[$track];

my $track_data = $disc->{trackinfo}->[$track];

return {
TIT1 => $disc->{genre},
TIT2 => $track_data->[0],
TRCK => $track+1,
TPE1 => $disc->{artist},
TALB => $disc->{cdname},
TYER => $disc->{year},
WXXX => $disc->{url},
COMM => $disc->{rest}||'',
};

}
# }}}



大規模加注標籤、大規模重新命名、剝離註釋和猜測曲目數量
autotag.pl 的主要功能是識別 MP3 文件。但在這個程序中,往往需要對很多組文件進行小的調整。輸入 Four Autotagging Horsemen。

剝離註釋是非常簡單的程序。我使用 get_tag() 獲得散列標籤,清空 COMM 和 WXXX 字段,以及使用 set_tag() 將該標籤寫回。實際上,註釋剝離可能已經通過大規模標籤加注完成了,但這個函數使用得非常頻繁,以至於使我感到有必要為它設定一個獨立的選項。

猜測曲目數量也使相當簡單的。獲取散列標籤,在該檔案和散列標籤上使用 guess_track_number() 函數,請求驗證,然後將該標籤寫回到文件中。

在一系列文件上對多個鍵(例如 TALB)進行大規模標籤加注操作。例如:

autotag.pl -mt "TALB=Best" *.mp3

於是,所有具有 mp3 副檔名的文件都在其 ID3v2 標籤中指定了 TALB 值。當您擁有某個藝術家的全部樂曲的目錄時,以及希望用該藝術家的名字標記所有這些樂曲時,採用大規模標籤加注的方式是非常合適的。只有受支持的標籤元素才可以大規模加注標籤。再一次進行這樣的程序:獲取散列標籤、進行修改,然後將它寫回。這樣做目的是使它的維護簡單便利。

清單 9. 大規模加注標籤、註釋剝離和猜測曲目數量

# {{{ handle the one-shot options
if ($config->GUESS_TRACK_NUMBERS_ONLY() ||
$config->STRIP_COMMENT_ONLY() ||
scalar keys %{$config->MASS_TAG_ONLY()})
{
foreach my $file (@ARGV)
{
my $tag = get_tag($file, 1);
unless (defined $tag)
{
warn "No ID3 TAG info in '$file', skipping";
next;
}

next if $config->DRYRUN();

# delegate stripping comments to the mass tagging function
if ($config->STRIP_COMMENT_ONLY())
{
$config->MASS_TAG_ONLY()->{COMM} = '';
$config->MASS_TAG_ONLY()->{WXXX} = '';
}

if (scalar keys %{$config->MASS_TAG_ONLY()})
{
foreach (keys %{$config->MASS_TAG_ONLY()})
{
unless (exists $supported_frames{$_})
{
warn "Unsupported tag element $_ requested for mass tagging, skipping";
next;
}
$tag->{$_} = $config->MASS_TAG_ONLY()->{$_};
}
set_tag($file, $tag);
}
else
{
my $track_number_guess = guess_track_number($file, $tag);

next if $config->DRYRUN();

if (defined $track_number_guess &&
read_yes_no("Is track number $track_number_guess OK for '$file'?", 1))
{
$tag->{TRCK} = $track_number_guess;
set_tag ($file, $tag);
}
else
{
warn "Could not guess a track number for file $file, sorry";
}
}
}

exit 0;
}
# }}}



噢,該介紹大規模重新命名選項了。我之所以將這個問題留在最後,是因為這個問題最複雜。對於每個重新命名參數而言,我將標籤值中的每個「%」都表示為「{{{%}}}」,因為不這樣的話,當後面跟隨一個特殊的重新命名參數時,「%」字元就可能被曲解。例如,用「100%true」作為曲目名,我們來看一看它如何變成「100%TRACKNAMErue」的,這裡 TRACKNAME 是我從該散列標籤中獲取的曲目名。

大規模重新命名也可消除不良的字元,代之以某些帶有「_」的字元,以確保檔案名合理。最後,除非通過指令行指出 -c ( accept_all )選項,否則 autotag.pl 將詢問是否可以對文件重新命名。

清單 10. 大規模重新命名

# {{{ handle the -rename_only option
if ($config->RENAME_ONLY())
{
foreach my $file (@ARGV)
{
my $tag = get_tag($file, 1);
# the extra parameter will ask us about upgrading V1 to V2
unless (defined $tag)
{
warn "No ID3 TAG info in '$file', skipping";
next;
}

my %map = (
'%c' => 'COMM',
'%s' => 'TIT2',
'%a' => 'TPE1',
'%t' => 'TALB',
'%n' => 'TRCK',
);

my $name = $config->RENAME_FORMAT();

foreach my $key (keys %map)
{
my $tagkey = $map{$key};
my $replacement = '';
if (exists $tag->{$tagkey})
{
$replacement = substr $tag->{$tagkey}, 0, $config->RENAME_MAX_CHARS();
# limit to N characters
if ($tagkey eq 'TRCK' && $replacement =~ m/^\d$/)
{
$replacement = "0$replacement";
}
}

$replacement =~ s/%/{{{%}}}/g;
# this is how we preserve %a in the fields, for example

$name =~ s/$key/$replacement/;
}

$name =~ s/{{{%}}}/%/g; # turn the {{{%}}} back into % in the fields

print "The name after % expansion is $name\n" if $config->DEBUG();

foreach my $char (map { quotemeta } @{$config->RENAME_BADCHARS()})
{
$name =~ s/$char//g;
}

print "The name after character removals is $name\n" if $config->DEBUG();

my $newchar = quotemeta $config->RENAME_REPLACEMENT();

foreach my $char (map { quotemeta } @{$config->RENAME_REPLACECHARS()})
{
$name =~ s/$char/$newchar/eg;
}

print "The name after character replacements is $name\n" if $config->DEBUG();


if ($name eq $file)
{
# do nothing
print "Renaming $file is unnecessary, it already answers to our high standards\n"
if $config->DEBUG();
}
elsif (-e $name)
{
warn "Could not use name $name, it's already taken by an existing
file or directory $file";
}
elsif ($config->ACCEPT_ALL() || read_yes_no("Is name $name OK for '$file'?", 1))
{
next if $config->DRYRUN();
print "Renaming $file -> $name\n";
rename($file, $name);
}
else
{
# do nothing
}
}

exit 0;
}
# }}}



結束語
本文的第 2 部分將討論 autotag.pl 的主循環,介紹該程序的一般用法。

參考資料

您可以參閱本文在 developerWorks 全球站點上的 英文原文.


閱讀 Ted 在 developerWorks 上的「功能豐富的 Perl」系列中的所有 Perl 文章。


下載 autotag application(為了執行該檔案,請重新命名為 autotag.pl)。


The CPAN module archive包含很多有用的 Perl 程序。


Ted 在專有的 CDDB / Gracenote上選項免費的 FreeDB project作為後端資料庫。


可以在 Open Directory 中找到有關很多不同 音瀕格式 (包括 MIDI、MP3 和 Ogg Vorbis)的資源。


如果您在使用 Term:Readline:Perl 時曾經遇到過麻煩,請嘗試使用 Term:Readline:GNU 。


id3lib library是用於讀、寫和操縱 ID3v1 和 ID3v2 標籤的。


MP3::Tag CPAN module 是用於讀 MP3 音瀕文件的標籤的。


Webservice::FreeDB CPAN module 通過搜尋關鍵字從 FreeDB 中獲取 列項。


MP3::ID3Lib CPAN module 使得您可以在 MP3 文件中編輯和增加 ID3 標籤 。


CPAN bundle安裝 MusicBrainz 客戶端機和所要求的模組。


AudioFile-Identify-MusicBrainz 是 MusicBrainz 服務的另一種 CPAN 接頭,是純 Perl 的 MusicBrainz 客戶端機實現。


IBM developerWorks 的文章「 Thinking XML: Manage metadata with MusicBrainz」討論了 MusicBrainz 服務的元資料問題。


AppConfig 是用於管理應用程式配置資訊的 Perl5 模組。


在 Ted 的專欄「 Application configuration with Perl」中可以瞭解更多有關 AppConfig 的訊息。


喜歡玩音響嗎?在 IBM developerWorks 的文章「 Introducing XHTML + Voice -- IBM's proposal to the W3C on developing multimodal UIs」和「 Multimodal applications」中,可以瞭解更多有關啟動聲音設備的內容。


您可能希望閱讀 IBM alphaWorks 上的文章 Voice Toolkit Preview。


噢,試一下 Music Sketcher吧,這是一個圖形化的作曲工具,它是由 IBM Research的多媒體專家編寫的。


關於作者
Teodor Zlatanov 於 1999 年獲取了波士頓大學電腦工程系碩士學位。他從 1992 年就開始做程序員,使用 Perl、Java、C 和 C++。他的興趣在於開放來源碼工作,致力於文本解析、3 層客戶端機-伺服器資料庫體系結構、UNIX 系統管理、CORBA 和專案目管理。可以通過 tzz@bu.edu 與他聯繫。
__________________
http://bbsimg.qianlong.com/upload/01/08/29/68/1082968_1136014649812.gif
psac 目前離線  
送花文章: 3, 收花文章: 1631 篇, 收花: 3205 次
有 2 位會員向 psac 送花:
Jerrybribe (2017-09-06),qdenise (2008-10-08)
感謝您發表一篇好文章