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是一样的所以那部分直接照搬了,注释也没改。