flac文件提取专辑封面手记

博客迁移后整理发型这篇文章当时没写完,不补了,不过还是得说明一些东西

下面这部分代码可用之处为从flac文件头开始然后各种形式的大跳,最后到达专辑封面的数据块,之后解析。

当时写的时候不会写图片解析部分,于是照搬了ShadowPlayer中某部分的代码,其有一特色为如果图片部分代码不认照样会爆搜然后尝试解析。实际上下面给出的代码的图片解析部分就是照搬的,而此处的正确做法恰恰不是如代码所示,我之前自己尝试能提取出图片的原因也就是爆搜成功了。。。

于是如果看官想要研究真正能用的代码,建议直接去看ShadowPlayer中此部分的代码。请参见这里

下面是之前写的原文:

这是代码(代码中的注释以及为了检查运行状态的奇怪提示没删,需要的话手动删除):

  1 /*
  2 fLaC标签图片提取库 Ver 0.0
  3 Gary 于2014/8/1 下午决定乱搞
  4 */
  5 
  6 #ifndef _ShadowPowerOff_FLACPIC___
  7 #define _ShadowPowerOff_FLACPIC___
  8 #define _CRT_SECURE_NO_WARNINGS  //安慰vs编译器用
  9 #ifndef NULL
 10 #define NULL 0
 11 #endif
 12 #include <cstdio>
 13 #include <cstdlib>
 14 #include <memory.h>
 15 #include <cstring>
 16 
 17 typedef unsigned char byte;
 18 using namespace std;
 19 
 20 namespace spFLAC {
 21     //fLaC标签头部结构体定义
 22     struct FLACHeader //似乎这就不用写成结构体咯,懒得改先用着
 23     {
 24         char  identi[4];//fLaC头部校验,必须为“fLaC”否则认为不存在fLaC标签
 25     };
 26 
 27     //fLaC标签METADATA_BLOCK_HEADER结构体定义
 28     struct FLACMetaDataHeader
 29     {                   //MBFlagType共1bit+7bit=1byte
 30         byte MBFlagType;//第一块1bit用于描述此MetaBlock是(1)不是(0)挨着音频块儿
 31                         //第二块7bit标志MetaBlock的种类的,其中6为PICTURE,别的用不着                 
 32         byte size[3]; //MetaBlock的大小,不包含 METADATA_BLOCK_HEADER大小 
 33     };
 34 
 35     //按照官方文档的说法,图片块儿和id3v2的应该是一样的,下面直接照搬电影同志的代码
 36     byte *pPicData = 0;        //指向图片数据的指针
 37     int picLength = 0;        //存放图片数据长度
 38     char picFormat[4] = {};    //存放图片数据的格式(扩展名)
 39 
 40     //检测图片格式,参数1:数据,参数2:指向存放文件格式(扩展名)的指针,返回值:是否成功(不是图片则失败)
 41     bool verificationPictureFormat(char *data)
 42     {
 43         //支持格式:JPEG/PNG/BMP/GIF
 44         byte jpeg[2] = { 0xff, 0xd8 };
 45         byte png[8] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
 46         byte gif[6] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 };
 47         byte gif2[6] = { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 };
 48         byte bmp[2] = { 0x42, 0x4d };
 49         memset(&picFormat, 0, 4);
 50         if (memcmp(data, &jpeg, 2) == 0)
 51         {
 52             strcpy(picFormat, "jpg");
 53         }
 54         else if (memcmp(data, &png, 8) == 0)
 55         {
 56             strcpy(picFormat, "png");
 57         }
 58         else if (memcmp(data, &gif, 6) == 0 || memcpy(data, &gif2, 6) == 0)
 59         {
 60             strcpy(picFormat, "gif");
 61         }
 62         else if (memcmp(data, &bmp, 2) == 0)
 63         {
 64             strcpy(picFormat, "bmp");
 65         }
 66         else
 67         {
 68             return false;
 69         }
 70 
 71         return true;
 72     }
 73 
 74     //安全释放内存
 75     void freePictureData()
 76     {
 77         if (pPicData)
 78         {
 79             delete pPicData;
 80         }
 81         pPicData = 0;
 82         picLength = 0;
 83         memset(&picFormat, 0, 4);
 84     }
 85 
 86     //将图片提取到内存,参数1:文件路径,成功返回true
 87     bool loadPictureData(const char *inFilePath)
 88     {
 89         freePictureData();
 90         FILE *fp = NULL;                //初始化文件指针,置空
 91         fp = fopen(inFilePath, "rb");    //以只读&二进制方式打开文件
 92         if (!fp)                        //如果打开失败
 93         {
 94             fp = NULL;
 95             return false;
 96         }
 97         fseek(fp, 0, SEEK_SET);            //设文件流指针到文件头部
 98 
 99         //读取
100         FLACHeader fLaCh;                //创建一个FLACHeader结构体(即char[4] = "fLaC")
101         memset(&fLaCh, 0, 4);            //内存填0,4个字节
102         fread(&fLaCh, 4, 1, fp);        //把文件头部4个字节写入结构体内存
103 
104         //文件头识别
105         if (strncmp(fLaCh.identi, "fLaC", 4) != 0)
106         {
107             fclose(fp);
108             fp = NULL;
109             return false;//没有fLaC标签
110         }
111 
112         //能运行到这里应该已经成功打开文件了
113         printf("是flac");
114         system("PAUSE");
115 
116         FLACMetaDataHeader fLaCfh;        //创建一个fLaCMetaBlockHeader结构体
117         memset(&fLaCfh, 0, 4); //共4byte,第一个字节上面说过了,后3bit记录标签实际内容(不含头)大小
118 
119         fread(&fLaCfh, 4, 1, fp);                //将数据写到fLaCMetaBlockHeader结构体中
120         int curDataLength = 4;                    //存放当前已经读取的数据大小,刚才已经读入4字节
121         while((fLaCfh.MBFlagType & 0x7F) != 6)  //如果标签不是6(即picture)则循环执行,
122         {
123             //计算帧数据长度
124             int frameLength = fLaCfh.size[0] * 0x10000 + fLaCfh.size[1] * 0x100 + fLaCfh.size[2];
125             fseek(fp, frameLength, SEEK_CUR);    //向前跳跃到下一个帧头
126             memset(&fLaCfh, 0, 4);                //清除帧头结构体数据
127             fread(&fLaCfh, 4, 1, fp);            //重新读取数据
128             curDataLength += frameLength + 4;    //记录当前所在的ID3标签位置,以便退出循环
129             printf("刚刚打劫了⑨,没掉出图包\n");
130             if ((fLaCfh.MBFlagType & 0x80) == 0x80) return false;//不包含图片标签,完事.0x80 = 10000000
131             system("PAUSE");
132             printf("再来一次\n");
133         }
134 
135         printf("正在处理掉落");
136         //计算一下当前图片帧的数据长度
137         int frameLength = fLaCfh.size[0] * 0x10000 + fLaCfh.size[1] * 0x100 + fLaCfh.size[2];
138 
139         /*
140         这是ID3v2.3图片帧的结构:
141 
142         <Header for 'Attached picture', ID: "APIC">
143         头部10个字节的帧头
144 
145         Text encoding      $xx
146         要跳过一个字节(文字编码)
147 
148         MIME type          <text string> $00
149         跳过(文本 + /0),这里可得到文件格式
150 
151         Picture type       $xx
152         跳过一个字节(图片类型)
153 
154         Description        <text string according to encoding> $00 (00)
155         跳过(文本 + /0),这里可得到描述信息
156 
157         Picture data       <binary data>
158         这是真正的图片数据
159         */
160         int nonPicDataLength = 0;    //非图片数据的长度
161         fseek(fp, 1, SEEK_CUR);        //信仰之跃
162         nonPicDataLength++;
163 
164         char tempData[20] = {};        //临时存放数据的空间
165         char mimeType[20] = {};        //图片类型
166         int mimeTypeLength = 0;        //图片类型文本长度
167 
168         fread(&tempData, 20, 1, fp);//取得一小段数据
169         fseek(fp, -20, SEEK_CUR);    //回到原位
170 
171         strcpy(mimeType, tempData);                //复制出一个字符串
172         mimeTypeLength = strlen(mimeType) + 1;    //测试字符串长度,补上末尾00
173         fseek(fp, mimeTypeLength, SEEK_CUR);    //跳到此数据之后
174         nonPicDataLength += mimeTypeLength;        //记录长度
175 
176         fseek(fp, 1, SEEK_CUR);        //再一次信仰之跃
177         nonPicDataLength++;
178 
179         int temp = 0;                //记录当前字节数据的变量
180         fread(&temp, 1, 1, fp);        //读取一个字节
181         nonPicDataLength++;            //+1
182         while (temp)                //循环到temp为0
183         {
184             fread(&temp, 1, 1, fp);    //如果不是0继续读一字节的数据
185             nonPicDataLength++;        //计数
186         }
187         //跳过了Description文本,以及末尾的\0
188 
189         //非主流情况检测
190         memset(tempData, 0, 20);
191         fread(&tempData, 8, 1, fp);
192         fseek(fp, -8, SEEK_CUR);    //回到原位
193         //判断40次,一位一位跳到文件头
194         bool ok = false;            //是否正确识别出文件头
195         for (int i = 0; i < 40; i++)
196         {
197             //校验文件头
198             if (verificationPictureFormat(tempData))
199             {
200                 ok = true;
201                 break;
202             }
203             else
204             {
205                 //如果校验失败尝试继续向后校验
206                 fseek(fp, 1, SEEK_CUR);
207                 nonPicDataLength++;
208                 fread(&tempData, 8, 1, fp);
209                 fseek(fp, -8, SEEK_CUR);
210             }
211         }
212 
213         if (!ok)
214         {
215             fclose(fp);
216             fp = NULL;
217             freePictureData();
218             return false;            //无法识别的数据
219         }
220         //-----真正的图片数据-----
221         picLength = frameLength - nonPicDataLength;        //计算图片数据长度
222         pPicData = new byte[picLength];                    //动态分配图片数据内存空间
223         memset(pPicData, 0, picLength);                    //清空图片数据内存
224         fread(pPicData, picLength, 1, fp);                //得到图片数据
225         //------------------------
226         fclose(fp);                                        //操作已完成,关闭文件。
227 
228         return true;
229     }
230 
231     //取得图片数据的长度
232     int getPictureLength()
233     {
234         return picLength;
235     }
236 
237     //取得指向图片数据的指针
238     byte *getPictureDataPtr()
239     {
240         return pPicData;
241     }
242 
243     //取得图片数据的扩展名(指针)
244     char *getPictureFormat()
245     {
246         return picFormat;
247     }
248 
249     bool writePictureDataToFile(const char *outFilePath)
250     {
251         FILE *fp = NULL;
252         if (picLength > 0)
253         {
254             fp = fopen(outFilePath, "wb");        //打开目标文件
255             if (fp)                                //打开成功
256             {
257                 fwrite(pPicData, picLength, 1, fp);    //写入文件
258                 fclose(fp);                            //关闭
259                 return true;
260             }
261             else
262             {
263                 return false;                        //文件打开失败
264             }
265         }
266         else
267         {
268             return false;                        //没有图像数据
269         }
270     }
271 
272     //提取图片文件,参数1:输入文件,参数2:输出文件,返回值:是否成功
273     bool extractPicture(const char *inFilePath, const char *outFilePath)
274     {
275         FILE *fp = NULL;                    //初始化文件指针,置空
276         if (loadPictureData(inFilePath))    //如果取得图片数据成功
277         {
278             if (writePictureDataToFile(outFilePath))
279             {
280                 return true;                //文件写出成功
281             }
282             else
283             {
284                 return false;                //文件写出失败
285             }
286         }
287         else
288         {
289             return false;                    //无图片数据
290         }
291         freePictureData();
292     }
293 }
294 #endif

调用方法(手动指定输入文件路径和输出文件路径,输出文件的格式自己猜吧~ gcc编译运行测试成功):

 1 #include "fLaCPic.h"
 2 
 3 int main(int argc, char* argv[])
 4 {
 5     using namespace spFLAC; 
 6     if (argc > 2)
 7     {
 8         extractPicture(argv[1], argv[2]);
 9     }
10     else
11     {
12         printf("参数数量不足");
13     }
14     return 0;
15 }

以上代码基于Shadow Player的ID3v2Pic.h头文件改造编写而成。由于flac格式的官方给出的说明文档上有说图片部分和id3v2是一样的所以那部分直接照搬了,注释也没改。

原文地址:https://www.cnblogs.com/blumia/p/fLaC_Tag_Get_Cover.html