原创Pascal程序:批量导出mp3内嵌专辑封面图片
icon2 Design of Design | icon4 2007-05-11 22:00| icon312 Comments | 本文内容遵从CC版权协议 转载请注明出自matrix67.com

英文发布页: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.

12 条回复

  • 楼层: 沙发 | | prism 说:

    gpc通不过编译
    error: module/unit interface `dos' could not be imported
    fpc可以(都是了Linux下)
    gpc没有dos库吧……
    matrix67用什么编译的?

  • 楼层: 板凳 | | prism 说:

    fpc编译之后运行,还不错,就是速度慢点。
    加油!

    回复:fpc2.0,在windows下编译的(因为我的mp3文件在windows下,我的ubuntu还没有安装ntfs写入工具)。
    速度慢是因为文件不是用二进制打开的,因此只能从前往后读。

    怎么这么快就有评论了……我发了日志还没校对完……看来先发日志后校对的习惯不好

  • 楼层: 地毯 | | dd 说:

    matrix67的代码风格,那叫一个赏心悦目。

    回复:哪里有dd牛啊,dd不是正在看code complete么

  • 楼层: 地板 | | 巫山霏云 说:

    呃...
    linux下面的ntfs写入工具会导致系统问题吧
    对fpc的开发表示关注...
    还是觉得pascal语言比c/c++看起来舒服...某种程度上

    回复:转c的最大困扰——代码怎么写都觉得难看

  • 楼层: 地下室 | | 巫山霏云 说:

    为了支持跨平台开发,我支持fpc
    为了给windows用户写出优秀的软件,我觉得delphi很好
    C++写多了其实还是很好看的,个人觉得
    反而是pascal有些东西真的很累赘了
    不过还是喜欢pascal

  • 楼层: 地基 | | 竹子的叶子 说:

    我一直都用Delphi,价格便宜量又足,好用极了(笑)
    为什么Matrix67牛不用呢?做软件的话,他和VC平分秋色啊………………

    回复:Delphi用过几次,还不太熟

  • 楼层: 地壳 | | Ai.Freedom 说:

    最好不要用rar格式压缩.. 除非你买了winrar.. 那个格式是收费的.

    回复:习惯了……其实国外都不怎么用winrar,7z现在很流行啊

  • 楼层: 地幔 | | oopos 说:

    呃...
    linux下面的ntfs写入工具会导致系统问题吧
    对fpc的开发表示关注...
    还是觉得pascal语言比c/c++看起来舒服...某种程度上

    可我怎么总觉得C比PASCAL好看呢,我正是因为这样才选C的。。。呵呵

  • 楼层: 地核 | | ninehole 说:

    請問一下有沒有c版本的 code?
    謝謝

  • 楼层: 10楼 | | coallin 说:

    需要此程序...最近整理歌的时候要备份专辑图片,现在是一个个用prtsc键截图下来存,脑残了。。。如果能提供个给偶会万分感谢~~

  • 楼层: 11楼 | | 桃花源居民 说:

    偶来了好多次M哥的博客了,这还是第一次留言呢。
    我以前一直没搞懂MP3的信息是怎么储存的,以为是什么神奇的压缩技术,结果可以直接提取(说这句话的时候巨寒~~)。
    现在发展太快,Pascal代码写着觉得特别死板。我是CQBS的高中生(老乡呵),我们学校现在搞竞赛都用C++了,不知M哥作何感想。

  • 楼层: 12楼 | | guocemike 说:

    “This program is a batch extractor which exports album covers embedded in mp3 ID3v2 tags.”这句话里的“IDv2”是不是应该改成“IDv1”啊?

您也随便说几句吧:

您可以在 Gravatar 设置您的头像。