博客遷移后整理發型這篇文章當時沒寫完,不補了,不過還是得說明一些東西
下面這部分代碼可用之處為從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是一樣的所以那部分直接照搬了,注釋也沒改。
