博客迁移后整理发型这篇文章当时没写完,不补了,不过还是得说明一些东西
下面这部分代码可用之处为从flac文件头开始然后各种形式的大跳,最后到达专辑封面的数据块,之后解析。
当时写的时候不会写图片解析部分,于是照搬了ShadowPlayer中某部分的代码,其有一特色为如果图片部分代码不认照样会爆搜然后尝试解析。实际上下面给出的代码的图片解析部分就是照搬的,而此处的正确做法恰恰不是如代码所示,我之前自己尝试能提取出图片的原因也就是爆搜成功了。。。
于是如果看官想要研究真正能用的代码,建议直接去看ShadowPlayer中此部分的代码。请参见这里。
下面是之前写的原文:
这是代码(代码中的注释以及为了检查运行状态的奇怪提示没删,需要的话手动删除):
/*
fLaC标签图片提取库 Ver 0.0
Gary 于2014/8/1 下午决定乱搞
*/ #ifndef _ShadowPowerOff_FLACPIC___
#define _ShadowPowerOff_FLACPIC___
#define _CRT_SECURE_NO_WARNINGS //安慰vs编译器用
#ifndef NULL
#define NULL 0
#endif
#include <cstdio>
#include <cstdlib>
#include <memory.h>
#include <cstring> typedef unsigned char byte;
using namespace std; namespace spFLAC {
//fLaC标签头部结构体定义
struct FLACHeader //似乎这就不用写成结构体咯,懒得改先用着
{
char identi[];//fLaC头部校验,必须为“fLaC”否则认为不存在fLaC标签
}; //fLaC标签METADATA_BLOCK_HEADER结构体定义
struct FLACMetaDataHeader
{ //MBFlagType共1bit+7bit=1byte
byte MBFlagType;//第一块1bit用于描述此MetaBlock是(1)不是(0)挨着音频块儿
//第二块7bit标志MetaBlock的种类的,其中6为PICTURE,别的用不着
byte size[]; //MetaBlock的大小,不包含 METADATA_BLOCK_HEADER大小
}; //按照官方文档的说法,图片块儿和id3v2的应该是一样的,下面直接照搬电影同志的代码
byte *pPicData = ; //指向图片数据的指针
int picLength = ; //存放图片数据长度
char picFormat[] = {}; //存放图片数据的格式(扩展名) //检测图片格式,参数1:数据,参数2:指向存放文件格式(扩展名)的指针,返回值:是否成功(不是图片则失败)
bool verificationPictureFormat(char *data)
{
//支持格式:JPEG/PNG/BMP/GIF
byte jpeg[] = { 0xff, 0xd8 };
byte png[] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
byte gif[] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 };
byte gif2[] = { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 };
byte bmp[] = { 0x42, 0x4d };
memset(&picFormat, , );
if (memcmp(data, &jpeg, ) == )
{
strcpy(picFormat, "jpg");
}
else if (memcmp(data, &png, ) == )
{
strcpy(picFormat, "png");
}
else if (memcmp(data, &gif, ) == || memcpy(data, &gif2, ) == )
{
strcpy(picFormat, "gif");
}
else if (memcmp(data, &bmp, ) == )
{
strcpy(picFormat, "bmp");
}
else
{
return false;
} return true;
} //安全释放内存
void freePictureData()
{
if (pPicData)
{
delete pPicData;
}
pPicData = ;
picLength = ;
memset(&picFormat, , );
} //将图片提取到内存,参数1:文件路径,成功返回true
bool loadPictureData(const char *inFilePath)
{
freePictureData();
FILE *fp = NULL; //初始化文件指针,置空
fp = fopen(inFilePath, "rb"); //以只读&二进制方式打开文件
if (!fp) //如果打开失败
{
fp = NULL;
return false;
}
fseek(fp, , SEEK_SET); //设文件流指针到文件头部 //读取
FLACHeader fLaCh; //创建一个FLACHeader结构体(即char[4] = "fLaC")
memset(&fLaCh, , ); //内存填0,4个字节
fread(&fLaCh, , , fp); //把文件头部4个字节写入结构体内存 //文件头识别
if (strncmp(fLaCh.identi, "fLaC", ) != )
{
fclose(fp);
fp = NULL;
return false;//没有fLaC标签
} //能运行到这里应该已经成功打开文件了
printf("是flac");
system("PAUSE"); FLACMetaDataHeader fLaCfh; //创建一个fLaCMetaBlockHeader结构体
memset(&fLaCfh, , ); //共4byte,第一个字节上面说过了,后3bit记录标签实际内容(不含头)大小 fread(&fLaCfh, , , fp); //将数据写到fLaCMetaBlockHeader结构体中
int curDataLength = ; //存放当前已经读取的数据大小,刚才已经读入4字节
while((fLaCfh.MBFlagType & 0x7F) != ) //如果标签不是6(即picture)则循环执行,
{
//计算帧数据长度
int frameLength = fLaCfh.size[] * 0x10000 + fLaCfh.size[] * 0x100 + fLaCfh.size[];
fseek(fp, frameLength, SEEK_CUR); //向前跳跃到下一个帧头
memset(&fLaCfh, , ); //清除帧头结构体数据
fread(&fLaCfh, , , fp); //重新读取数据
curDataLength += frameLength + ; //记录当前所在的ID3标签位置,以便退出循环
printf("刚刚打劫了⑨,没掉出图包\n");
if ((fLaCfh.MBFlagType & 0x80) == 0x80) return false;//不包含图片标签,完事.0x80 = 10000000
system("PAUSE");
printf("再来一次\n");
} printf("正在处理掉落");
//计算一下当前图片帧的数据长度
int frameLength = fLaCfh.size[] * 0x10000 + fLaCfh.size[] * 0x100 + fLaCfh.size[]; /*
这是ID3v2.3图片帧的结构: <Header for 'Attached picture', ID: "APIC">
头部10个字节的帧头 Text encoding $xx
要跳过一个字节(文字编码) MIME type <text string> $00
跳过(文本 + /0),这里可得到文件格式 Picture type $xx
跳过一个字节(图片类型) Description <text string according to encoding> $00 (00)
跳过(文本 + /0),这里可得到描述信息 Picture data <binary data>
这是真正的图片数据
*/
int nonPicDataLength = ; //非图片数据的长度
fseek(fp, , SEEK_CUR); //信仰之跃
nonPicDataLength++; char tempData[] = {}; //临时存放数据的空间
char mimeType[] = {}; //图片类型
int mimeTypeLength = ; //图片类型文本长度 fread(&tempData, , , fp);//取得一小段数据
fseek(fp, -, SEEK_CUR); //回到原位 strcpy(mimeType, tempData); //复制出一个字符串
mimeTypeLength = strlen(mimeType) + ; //测试字符串长度,补上末尾00
fseek(fp, mimeTypeLength, SEEK_CUR); //跳到此数据之后
nonPicDataLength += mimeTypeLength; //记录长度 fseek(fp, , SEEK_CUR); //再一次信仰之跃
nonPicDataLength++; int temp = ; //记录当前字节数据的变量
fread(&temp, , , fp); //读取一个字节
nonPicDataLength++; //+1
while (temp) //循环到temp为0
{
fread(&temp, , , fp); //如果不是0继续读一字节的数据
nonPicDataLength++; //计数
}
//跳过了Description文本,以及末尾的\0 //非主流情况检测
memset(tempData, , );
fread(&tempData, , , fp);
fseek(fp, -, SEEK_CUR); //回到原位
//判断40次,一位一位跳到文件头
bool ok = false; //是否正确识别出文件头
for (int i = ; i < ; i++)
{
//校验文件头
if (verificationPictureFormat(tempData))
{
ok = true;
break;
}
else
{
//如果校验失败尝试继续向后校验
fseek(fp, , SEEK_CUR);
nonPicDataLength++;
fread(&tempData, , , fp);
fseek(fp, -, SEEK_CUR);
}
} if (!ok)
{
fclose(fp);
fp = NULL;
freePictureData();
return false; //无法识别的数据
}
//-----真正的图片数据-----
picLength = frameLength - nonPicDataLength; //计算图片数据长度
pPicData = new byte[picLength]; //动态分配图片数据内存空间
memset(pPicData, , picLength); //清空图片数据内存
fread(pPicData, picLength, , fp); //得到图片数据
//------------------------
fclose(fp); //操作已完成,关闭文件。 return true;
} //取得图片数据的长度
int getPictureLength()
{
return picLength;
} //取得指向图片数据的指针
byte *getPictureDataPtr()
{
return pPicData;
} //取得图片数据的扩展名(指针)
char *getPictureFormat()
{
return picFormat;
} bool writePictureDataToFile(const char *outFilePath)
{
FILE *fp = NULL;
if (picLength > )
{
fp = fopen(outFilePath, "wb"); //打开目标文件
if (fp) //打开成功
{
fwrite(pPicData, picLength, , fp); //写入文件
fclose(fp); //关闭
return true;
}
else
{
return false; //文件打开失败
}
}
else
{
return false; //没有图像数据
}
} //提取图片文件,参数1:输入文件,参数2:输出文件,返回值:是否成功
bool extractPicture(const char *inFilePath, const char *outFilePath)
{
FILE *fp = NULL; //初始化文件指针,置空
if (loadPictureData(inFilePath)) //如果取得图片数据成功
{
if (writePictureDataToFile(outFilePath))
{
return true; //文件写出成功
}
else
{
return false; //文件写出失败
}
}
else
{
return false; //无图片数据
}
freePictureData();
}
}
#endif
调用方法(手动指定输入文件路径和输出文件路径,输出文件的格式自己猜吧~ gcc编译运行测试成功):
#include "fLaCPic.h" int main(int argc, char* argv[])
{
using namespace spFLAC;
if (argc > )
{
extractPicture(argv[], argv[]);
}
else
{
printf("参数数量不足");
}
return ;
}
以上代码基于Shadow Player的ID3v2Pic.h头文件改造编写而成。由于flac格式的官方给出的说明文档上有说图片部分和id3v2是一样的所以那部分直接照搬了,注释也没改。