zoukankan      html  css  js  c++  java
  • 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是一样的所以那部分直接照搬了,注释也没改。

  • 相关阅读:
    sqlserver tips
    mysql tips
    小知识点集锦
    设计模式
    将微博或者qq空间的说说同步至博客园 wcf+js(ajax)跨域请求(1)
    WCF服务寄宿IIS与Windows服务
    C# 基础小知识之yield 关键字
    WPF命令绑定 自定义命令
    KnockOut 绑定之foreach绑定(mvc+knockout)
    P5019 铺设道路
  • 原文地址:https://www.cnblogs.com/blumia/p/fLaC_Tag_Get_Cover.html
Copyright © 2011-2022 走看看