英文发布页:http://www.matrix67.com/blog/article.asp?id=259
0.11版发布,主要更新如下:
- 使用二进制方式打开文件,加快程序运行速度(处理我的600多个mp3文件不到半分钟)
- 自动检测重复专辑,同一专辑只写一次文件
- 修正当ID3v1中有发行年份、歌曲类型等信息时发生的错误
- 修正找不到ID3v1时歌曲名出现乱码的错误
- 修正没有专辑图片时错误输出文件的问题
- 修正查找文件时漏掉部分文件的错误
- 识别更多的图片类型
- 其它一些小的修改
点击此处下载
转移系统平台后我试用了一大柄的音乐播放软件,最后还是选择了RhythmBox。对于我来说,RhythmBox的唯一缺点就是不支持mp3内部的专辑图片,而我的mp3文件全部都是标签嵌入的图片。RhythmBox可以通过读取~/.gnome2/rhythmbox/covers下“歌手 - 专辑名”格式的文件名来显示图片,于是我想到找个软件来批量导出专辑图片。没想到我把google翻了个底朝天都没找到这样的程序,一狠心打算自己写一个。
这个程序估计会有需要用的人,因此作为0.1 beta版发布在这里。现在已经发布了0.11版。我对这个小程序的开发比较感兴趣,任何觉得有需求的人可以在下面留言。发现Bug请帮忙报告一下(这句话说了N多次都没人回应)。
程序是在Windows下编写的。和上次一样,目前暂时不打算用Delphi(除非我要接着做下去)。C语言的文件操作还不怎么过关,只好又拿Free Pascal写了。
使用方法(请仔细阅读,发生任何意外我不负责!)
由于这是一个测试版,请先备份好你的mp3文件以防不测(其实一般不会发生问题,程序不写mp3文件)。下载该rar文件并解压,你会看到一个.exe文件。 将这个文件拷贝至你的mp3目录下,运行该文件后程序将扫描该目录下的所有mp3文件并寻找可能的内嵌图片,以ID3v1信息来命名图片文件,文件名格式为“歌手 - 专辑名”。相同文件名自动覆盖(这样的话你的专辑图片就是唯一的)。之所以不用ID3v2是因为ID3v2的编码不确定,我暂时不想处理Unicode。如果你的mp3里没有ID3v1标签,你可以随便找一个工具把ID3v2转为ID3v1,推荐用ID3-TagIt。建议你先复制少许mp3文件到新的文件夹试用一下。
以下情况可能导致错误:
1. mp3文件里嵌入多个图片或有图片说明(如果你是iTunes用户应该没问题);
2. ID3v1里有Windows不允许作为文件名的字符;
3. 不存在ID3v1或需要的ID3v1信息不全。
发生后两种情况时,文件名将用FilenameError加数字编号代替。
Matrix67原创
转贴请注明出处
如果有人感兴趣的话,附程序代码:program pic_extractor;
// -------------------------
// Current Version : 0.11
// Last Update : 2007-05-31
// Author : Matrix67.com
// -------------------------
// ----------------------------------------------------
// This program is a batch extractor which exports album
// covers embedded in mp3 ID3v2 tags. Compiled version can
// be found at http://www.matrix67.com/blog/article.asp?id=240
// Version 0.11 tested under Windows XP SP2, Free Pascal 2.0
// Next version will be written in Delphi.
// ----------------------------------------------------
uses dos,sysutils;
var
PictureType: string; // .png|.jpg|.bmp|.gif
FailCount : longint=0; // Number of Invalid Filenames
AlbumCount : longint=0; // Number of Albums
FileHandle : longint;
PicBuffer : array[1..1100000]of char;
InfoBuffer : array[1..210]of char;
FileNameDone: array[1..1100]of string[70];
// FileNameDone[AlbumCount]:=OutputFileName
function SameAlbum(OutputFileName:string):boolean;
var
i:longint;
begin
for i:=1 to AlbumCount do
if FileNameDone[i] = OutputFileName then exit(true);
inc(AlbumCount);
FileNameDone[ AlbumCount ] :=OutputFileName;
exit(false);
end;
function FindAPIC:longint;
// --------------------------------------------------------------
// This function will search the entire tag for APIC tag frame.
// Error may occur if there happens to be another 'APIC' string.
// Bugs will be fixed in next version.
// --------------------------------------------------------------
var
Scanner:string=' ';
FrameLength : longint=0;
Id3Length : longint=0;
identifier : integer;
procedure ReadFrame(var ch:char);
begin
FileRead( FileHandle , ch , SizeOf(ch) );
dec(FrameLength);
end;
var
i:longint;
ch:char;
begin
// Check if ID3 Tag Exists
FileRead( FileHandle, identifier, 2);
if identifier<>$4449 then exit(-1);
// Get Size of ID3 Tag
FileSeek( FileHandle, 4, fsFromCurrent);
for i:=1 to 4 do
begin
FileRead( FileHandle , ch , SizeOf(ch) );
Id3Length:=Id3Length shl 7 + ord(ch);
end;
// Search for APIC header
repeat
FileRead( FileHandle , ch , SizeOf(ch) );
dec(Id3Length);
delete(Scanner, 1, 1);
Scanner:=Scanner+ch;
if Scanner='APIC' then break;
until Id3Length=0;
if Scanner<>'APIC' then exit(-1);
// Calculate size of APIC frame
for i:=1 to 4 do
begin
FileRead( FileHandle , ch , SizeOf(ch) );
FrameLength:=FrameLength shl 8 + ord(ch);
end;
for i:=1 to 2 do FileRead( FileHandle , ch , SizeOf(ch) );
// Filename Extension
repeat ReadFrame(ch) until ch='/';
ReadFrame(ch);
if ch='j' then PictureType:='jpg'
else if ch='p' then PictureType:='png'
else if ch='b' then PictureType:='bmp'
else if ch='g' then PictureType:='gif'
else PictureType:='ukn'; // Unknown
// Drop some useless bits.
// Error may occur if any comment or type description exists.
repeat ReadFrame(ch) until ch=#0;
for i:=1 to 2 do ReadFrame(ch);
FileRead( FileHandle , PicBuffer , FrameLength );
exit(FrameLength);
end;
function FindOutputFileName:string;
// ------------------------------------------------------------------
// This function reads the very end of file for getting ID3v1 tag.
// Filename will be generated using "Artist - Album" format. We use ID3v1
// instead of ID3v2 because the encoding of ID3v2 may vary. This program
// can only handle GBK encodings. Files without ID3v1 lead to invalid
// filenames.
// ------------------------------------------------------------------
var
FindResult:string='';
i:longint;
begin
FileSeek( FileHandle , -128 , fsFromEnd );
FileRead( FileHandle , InfoBuffer , 128 );
// Identifier of ID3v1
// If no ID3v1 found, exit invalid characters for filename.
if InfoBuffer[1]+InfoBuffer[2]+InfoBuffer[3]<>'TAG' then exit('? - ?');
// Name of Album
i:=34;
while InfoBuffer[i]<>#0 do
begin
FindResult:=FindResult+InfoBuffer[i];
inc(i);
end;
// Filename Format : Artist - Album
// This format fits Rhythmbox well
// Format can be customized in further
FindResult:=FindResult + ' - ';
// Name of Artist
i:=64;
while InfoBuffer[i]<>#0 do
begin
FindResult:=FindResult+InfoBuffer[i];
inc(i);
end;
exit(FindResult);
end;
procedure SavePic( var OutputFileName:string; FrameLength:longint );
// ------------------------------------------------------
// Save the extracted picture with the filename provided
// by FindOutputFileName Function. Output directory and
// overwrite option will be added in GUI version.
// ------------------------------------------------------
var
FailedNum:string;
begin
FileHandle:=FileCreate(OutputFileName + '.' + PictureType);
if FileHandle=-1 then
begin
// Error caused by invalid characters, Changing Filename
inc(FailCount);
str(FailCount,FailedNum);
OutputFileName:='FilenameError' + FailedNum;
FileHandle:=FileCreate(OutputFileName + '.' + PictureType);
end;
FileWrite( FileHandle , PicBuffer , FrameLength );
FileClose( FileHandle );
end;
procedure main;
var
FrameLength : longint;
OutputFileName : string;
LastFileName : string;
dir:TSearchRec;
begin
FindFirst( '*.mp3', faAnyFile and not (faVolumeID or faDirectory), Dir);
while (DosError=0) do
begin
LastFileName:=Dir.name;
FileHandle:=FileOpen(Dir.name,fmOpenRead);
if FileHandle=-1 then halt;
write(Dir.name + ' --> ');
FrameLength := FindAPIC;
OutputFileName := FindOutputFileName;
FileClose( FileHandle );
if SameAlbum(OutputFileName) then
writeln('Same Album Cover Already Extracted.')
else if FrameLength=-1 then
writeln('Album Cover Not Found.')
else begin
SavePic(OutputFileName , FrameLength);
write('Found Album Cover, Saved as "');
writeln(OutputFileName + '.' + PictureType + '"');
end;
FindNext(Dir);
if Dir.name=LastFileName then halt;
end;
FindClose(Dir);
end;
begin
main;
end.

gpc通不过编译
error: module/unit interface `dos' could not be imported
fpc可以(都是了Linux下)
gpc没有dos库吧……
matrix67用什么编译的?
fpc编译之后运行,还不错,就是速度慢点。
加油!
回复:fpc2.0,在windows下编译的(因为我的mp3文件在windows下,我的ubuntu还没有安装ntfs写入工具)。
速度慢是因为文件不是用二进制打开的,因此只能从前往后读。
怎么这么快就有评论了……我发了日志还没校对完……看来先发日志后校对的习惯不好
matrix67的代码风格,那叫一个赏心悦目。
回复:哪里有dd牛啊,dd不是正在看code complete么
呃...
linux下面的ntfs写入工具会导致系统问题吧
对fpc的开发表示关注...
还是觉得pascal语言比c/c++看起来舒服...某种程度上
回复:转c的最大困扰——代码怎么写都觉得难看
为了支持跨平台开发,我支持fpc
为了给windows用户写出优秀的软件,我觉得delphi很好
C++写多了其实还是很好看的,个人觉得
反而是pascal有些东西真的很累赘了
不过还是喜欢pascal
我一直都用Delphi,价格便宜量又足,好用极了(笑)
为什么Matrix67牛不用呢?做软件的话,他和VC平分秋色啊………………
回复:Delphi用过几次,还不太熟
最好不要用rar格式压缩.. 除非你买了winrar.. 那个格式是收费的.
回复:习惯了……其实国外都不怎么用winrar,7z现在很流行啊
可我怎么总觉得C比PASCAL好看呢,我正是因为这样才选C的。。。呵呵
請問一下有沒有c版本的 code?
謝謝
需要此程序...最近整理歌的时候要备份专辑图片,现在是一个个用prtsc键截图下来存,脑残了。。。如果能提供个给偶会万分感谢~~
偶来了好多次M哥的博客了,这还是第一次留言呢。
我以前一直没搞懂MP3的信息是怎么储存的,以为是什么神奇的压缩技术,结果可以直接提取(说这句话的时候巨寒~~)。
现在发展太快,Pascal代码写着觉得特别死板。我是CQBS的高中生(老乡呵),我们学校现在搞竞赛都用C++了,不知M哥作何感想。
“This program is a batch extractor which exports album covers embedded in mp3 ID3v2 tags.”这句话里的“IDv2”是不是应该改成“IDv1”啊?
和楼上的说下,封面就是放在ID3v2 里的,不是ID3v1 里。
和楼上的说下,封面就是放在ID3v2 里的,不是ID3v1 里。看了MP3文件格式的标准。
最近在想用java写这个一个程序,正在研究中......
请问这段代码是按照什么协议发布的?
想写一个类似的东西
太感谢啦,楼主暴有才啊,我想了很多办法提取内嵌图片都失败了,今天总算解决了,再次感谢楼主!!!
我的音乐都是以专辑名命名的文件夹 发现自己用不了 如果要开发 能不能包括下根目录
What libretaing knowledge. Give me liberty or give me death.