From ce885bee3f7f4171c10918f14b38a50f2f5ab388 Mon Sep 17 00:00:00 2001 From: Gary Wang Date: Thu, 9 Apr 2020 18:12:35 +0800 Subject: [PATCH] feat: album cover support copy-paste from the old SP codebase --- CMakeLists.txt | 4 + FlacPic.h | 264 ++++++++++++++++++++++++++++++ ID3v2Pic.h | 430 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 14 ++ mainwindow.cpp | 55 ++++++- mainwindow.h | 2 + mainwindow.ui | 6 + 7 files changed, 773 insertions(+), 2 deletions(-) create mode 100755 FlacPic.h create mode 100755 ID3v2Pic.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ce8051e..2ffabb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,10 @@ add_executable(${EXE_NAME} playlistmodel.cpp mainwindow.ui resources.qrc + + # 3rd party code + FlacPic.h + ID3v2Pic.h ) target_link_libraries(${EXE_NAME} PRIVATE Qt5::Widgets Qt5::Multimedia) diff --git a/FlacPic.h b/FlacPic.h new file mode 100755 index 0000000..2bb6462 --- /dev/null +++ b/FlacPic.h @@ -0,0 +1,264 @@ +/* +FLAC标签图片提取库 Ver 1.0 +从FLAC文件中稳定、快捷、高效、便捷地提取出图片数据 +支持BMP、JPEG、PNG、GIF图片格式 +可将图片数据提取到文件或内存中,并能安全地释放内存 +使用方式与ID3v2版本相同 +ShadowPower 于2014/8/1 夜间 +*/ + +#ifndef _ShadowPower_FLACPIC___ +#define _ShadowPower_FLACPIC___ +#define _CRT_SECURE_NO_WARNINGS +#ifndef NULL +#define NULL 0 +#endif +#include +#include +#include +#include + +typedef unsigned char byte; +using namespace std; + +namespace spFLAC { + //Flac元数据块头部结构体定义 + struct FlacMetadataBlockHeader + { + byte flag; //标志位,高1位:是否为最后一个数据块,低7位:数据块类型 + byte length[3]; //数据块长度,不含数据块头部 + }; + + byte *pPicData = 0; //指向图片数据的指针 + int picLength = 0; //存放图片数据长度 + char picFormat[4] = {}; //存放图片数据的格式(扩展名) + + //检测图片格式,参数1:数据,返回值:是否成功(不是图片则失败) + bool verificationPictureFormat(char *data) + { + //支持格式:JPEG/PNG/BMP/GIF + byte jpeg[2] = { 0xff, 0xd8 }; + byte png[8] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a }; + byte gif[6] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }; + byte gif2[6] = { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }; + byte bmp[2] = { 0x42, 0x4d }; + memset(&picFormat, 0, 4); + if (memcmp(data, &jpeg, 2) == 0) + { + strcpy(picFormat, "jpg"); + } + else if (memcmp(data, &png, 8) == 0) + { + strcpy(picFormat, "png"); + } + else if (memcmp(data, &gif, 6) == 0 || memcmp(data, &gif2, 6) == 0) + { + strcpy(picFormat, "gif"); + } + else if (memcmp(data, &bmp, 2) == 0) + { + strcpy(picFormat, "bmp"); + } + else + { + return false; + } + + return true; + } + + //安全释放内存 + void freePictureData() + { + if (pPicData) + { + delete pPicData; + } + pPicData = 0; + picLength = 0; + memset(&picFormat, 0, 4); + } + + //将图片提取到内存,参数1:文件路径,成功返回true + bool loadPictureData(const char *inFilePath) + { + freePictureData(); + FILE *fp = NULL; + fp = fopen(inFilePath, "rb"); + if (!fp) //如果打开失败 + { + fp = NULL; + return false; + } + fseek(fp, 0, SEEK_SET); //设文件流指针到文件头部 + byte magic[4] = {}; //存放校验数据 + memset(&magic, 0, 4); + fread(&magic, 4, 1, fp); //读入校验数据 + byte fLaC[4] = { 0x66, 0x4c, 0x61, 0x43 }; + if (memcmp(&magic, &fLaC, 4) == 0) + { + //数据校验正确,文件类型为Flac + FlacMetadataBlockHeader fmbh; //创建Flac元数据块头部结构体 + memset(&fmbh, 0, 4); //清空内存 + fread(&fmbh, 4, 1, fp); //读入头部数据 + //计算数据块长度,不含头部 + int blockLength = fmbh.length[0] * 0x10000 + fmbh.length[1] * 0x100 + fmbh.length[2]; + int loopCount = 0; //循环计数,防死 + while ((fmbh.flag & 0x7f) != 6) + { + //如果数据类型不是图片,此处循环执行 + loopCount++; + if (loopCount > 40) + { + //循环40次没有遇到末尾就直接停止 + fclose(fp); + fp = NULL; + return false; //可能文件不正常 + } + fseek(fp, blockLength, SEEK_CUR); //跳过数据块 + if ((fmbh.flag & 0x80) == 0x80) + { + //已经是最后一个数据块了,仍然不是图片 + fclose(fp); + fp = NULL; + return false; //没有找到图片数据 + } + //取得下一数据块头部 + memset(&fmbh, 0, 4); //清空内存 + fread(&fmbh, 4, 1, fp); //读入头部数据 + blockLength = fmbh.length[0] * 0x10000 + fmbh.length[1] * 0x100 + fmbh.length[2];//计算数据块长度 + } + //此时已到图片数据块 + + int nonPicDataLength = 0; //非图片数据长度 + fseek(fp, 4, SEEK_CUR); //信仰之跃 + nonPicDataLength += 4; + char nextJumpLength[4]; //下次要跳的长度 + fread(&nextJumpLength, 4, 1, fp); //读取安全跳跃距离 + nonPicDataLength += 4; + int jumpLength = nextJumpLength[0] * 0x1000000 + nextJumpLength[1] * 0x10000 + nextJumpLength[2] * 0x100 + nextJumpLength[3];//计算数据块长度 + fseek(fp, jumpLength, SEEK_CUR); //Let's Jump!! + nonPicDataLength += jumpLength; + fread(&nextJumpLength, 4, 1, fp); + nonPicDataLength += 4; + jumpLength = nextJumpLength[0] * 0x1000000 + nextJumpLength[1] * 0x10000 + nextJumpLength[2] * 0x100 + nextJumpLength[3]; + fseek(fp, jumpLength, SEEK_CUR); //Let's Jump too!! + nonPicDataLength += jumpLength; + fseek(fp, 20, SEEK_CUR); //信仰之跃 + nonPicDataLength += 20; + + //非主流情况检测+获得文件格式 + char tempData[20] = {}; + memset(tempData, 0, 20); + fread(&tempData, 8, 1, fp); + fseek(fp, -8, SEEK_CUR); //回到原位 + //判断40次,一位一位跳到文件头 + bool ok = false; //是否正确识别出文件头 + for (int i = 0; i < 40; i++) + { + //校验文件头 + if (verificationPictureFormat(tempData)) + { + ok = true; + break; + } + else + { + //如果校验失败尝试继续向后校验 + fseek(fp, 1, SEEK_CUR); + nonPicDataLength++; + fread(&tempData, 8, 1, fp); + fseek(fp, -8, SEEK_CUR); + } + } + + if (!ok) + { + fclose(fp); + fp = NULL; + freePictureData(); + return false; //无法识别的数据 + } + + //-----抵达图片数据区----- + picLength = blockLength - nonPicDataLength; //计算图片数据长度 + pPicData = new byte[picLength]; //动态分配图片数据内存空间 + memset(pPicData, 0, picLength); //清空图片数据内存 + fread(pPicData, picLength, 1, fp); //得到图片数据 + //------------------------ + fclose(fp); //操作已完成,关闭文件。 + } + else + { + //校验失败,不是Flac + fclose(fp); + fp = NULL; + freePictureData(); + return false; + } + return true; + } + + //取得图片数据的长度 + int getPictureLength() + { + return picLength; + } + + //取得指向图片数据的指针 + byte *getPictureDataPtr() + { + return pPicData; + } + + //取得图片数据的扩展名(指针) + char *getPictureFormat() + { + return picFormat; + } + + bool writePictureDataToFile(const char *outFilePath) + { + FILE *fp = NULL; + if (picLength > 0) + { + fp = fopen(outFilePath, "wb"); //打开目标文件 + if (fp) //打开成功 + { + fwrite(pPicData, picLength, 1, fp); //写入文件 + fclose(fp); //关闭 + return true; + } + else + { + return false; //文件打开失败 + } + } + else + { + return false; //没有图像数据 + } + } + + //提取图片文件,参数1:输入文件,参数2:输出文件,返回值:是否成功 + bool extractPicture(const char *inFilePath, const char *outFilePath) + { + if (loadPictureData(inFilePath)) //如果取得图片数据成功 + { + if (writePictureDataToFile(outFilePath)) + { + return true; //文件写出成功 + } + else + { + return false; //文件写出失败 + } + } + else + { + return false; //无图片数据 + } + freePictureData(); + } +} +#endif \ No newline at end of file diff --git a/ID3v2Pic.h b/ID3v2Pic.h new file mode 100755 index 0000000..3dab73d --- /dev/null +++ b/ID3v2Pic.h @@ -0,0 +1,430 @@ +/* +ID3v2标签图片提取库 Ver 1.0 +支持ID3v2所有版本 +从ID3v2标签中稳定、快捷、高效、便捷地提取出图片数据 +支持BMP、JPEG、PNG、GIF图片格式 +可将图片数据提取到文件或内存中,并能安全地释放内存 +ShadowPower 于2014/8/1 +*/ + +#ifndef _ShadowPower_ID3V2PIC___ +#define _ShadowPower_ID3V2PIC___ +#define _CRT_SECURE_NO_WARNINGS +#ifndef NULL +#define NULL 0 +#endif +#include +#include +#include +#include + +typedef unsigned char byte; +using namespace std; + +namespace spID3 { + //ID3v2标签头部结构体定义 + struct ID3V2Header + { + char identi[3];//ID3头部校验,必须为“ID3”否则认为不存在ID3标签 + byte major; //ID3版本号,3是ID3v2.3,4是ID3v2.4,以此类推 + byte revsion; //ID3副版本号,此版本为00 + byte flags; //标志位 + byte size[4]; //标签大小,不含标签头的10个字节 + }; + + //ID3v2标签帧头部结构体定义 + struct ID3V2FrameHeader + { + char FrameId[4];//标识符,用于描述此标签帧的内容类型 + byte size[4]; //标签帧的大小,不含标签头的10个字节 + byte flags[2]; //标志位 + }; + + struct ID3V22FrameHeader + { + char FrameId[3];//标识符,用于描述此标签帧的内容类型 + byte size[3]; //标签帧的大小,不含标签头的6个字节 + }; + + byte *pPicData = 0; //指向图片数据的指针 + int picLength = 0; //存放图片数据长度 + char picFormat[4] = {}; //存放图片数据的格式(扩展名) + + //检测图片格式,参数1:数据,返回值:是否成功(不是图片则失败) + bool verificationPictureFormat(char *data) + { + //支持格式:JPEG/PNG/BMP/GIF + byte jpeg[2] = { 0xff, 0xd8 }; + byte png[8] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a }; + byte gif[6] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }; + byte gif2[6] = { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 }; + byte bmp[2] = { 0x42, 0x4d }; + memset(&picFormat, 0, 4); + if (memcmp(data, &jpeg, 2) == 0) + { + strcpy(picFormat, "jpg"); + } + else if (memcmp(data, &png, 8) == 0) + { + strcpy(picFormat, "png"); + } + else if (memcmp(data, &gif, 6) == 0 || memcmp(data, &gif2, 6) == 0) + { + strcpy(picFormat, "gif"); + } + else if (memcmp(data, &bmp, 2) == 0) + { + strcpy(picFormat, "bmp"); + } + else + { + return false; + } + + return true; + } + + //安全释放内存 + void freePictureData() + { + if (pPicData) + { + delete pPicData; + } + pPicData = 0; + picLength = 0; + memset(&picFormat, 0, 4); + } + + //将图片提取到内存,参数1:文件路径,成功返回true + bool loadPictureData(const char *inFilePath) + { + freePictureData(); + FILE *fp = NULL; //初始化文件指针,置空 + fp = fopen(inFilePath, "rb"); //以只读&二进制方式打开文件 + if (!fp) //如果打开失败 + { + fp = NULL; + return false; + } + fseek(fp, 0, SEEK_SET); //设文件流指针到文件头部(印象中打开之后默认在尾部) + + //读取 + ID3V2Header id3v2h; //创建一个ID3v2标签头结构体 + memset(&id3v2h, 0, 10); //内存填0,10个字节 + fread(&id3v2h, 10, 1, fp); //把文件头部10个字节写入结构体内存 + + //文件头识别 + if (strncmp(id3v2h.identi, "ID3", 3) != 0) + { + fclose(fp); + fp = NULL; + return false;//没有ID3标签 + } + + //能运行到这里应该已经成功打开文件了 + + //计算整个标签长度,每个字节仅7位有效 + int tagTotalLength = (id3v2h.size[0] & 0x7f) * 0x200000 + (id3v2h.size[1] & 0x7f) * 0x4000 + (id3v2h.size[2] & 0x7f) * 0x80 + (id3v2h.size[3] & 0x7f); + + if (id3v2h.major == 3 || id3v2h.major == 4) //ID3v2.3 或 ID3v2.4 + { + ID3V2FrameHeader id3v2fh; //创建一个ID3v2标签帧头结构体 + memset(&id3v2fh, 0, 10); + + bool hasExtendedHeader = ((id3v2h.flags >> 6 & 0x1) == 1);//是否有扩展头 + + if (hasExtendedHeader) + { + //如果有扩展头 + byte extendedHeaderSize[4] = {}; + memset(&extendedHeaderSize, 0, 4); + fread(&extendedHeaderSize, 4, 1, fp); + //取得扩展头大小(不含以上数据) + int extendedHeaderLength = extendedHeaderSize[0] * 0x1000000 + extendedHeaderSize[1] * 0x10000 + extendedHeaderSize[2] * 0x100 + extendedHeaderSize[3]; + //跳过扩展头 + fseek(fp, extendedHeaderLength, SEEK_CUR); + } + + fread(&id3v2fh, 10, 1, fp); //将数据写到ID3V2FrameHeader结构体中 + int curDataLength = 10; //存放当前已经读取的数据大小,刚才已经读入10字节 + while ((strncmp(id3v2fh.FrameId, "APIC", 4) != 0))//如果帧头没有APIC标识符则循环执行 + { + if (curDataLength > tagTotalLength) + { + fclose(fp); + fp = NULL; + return false; //未发现图片数据 + } + //计算帧数据长度 + int frameLength = id3v2fh.size[0] * 0x1000000 + id3v2fh.size[1] * 0x10000 + id3v2fh.size[2] * 0x100 + id3v2fh.size[3]; + fseek(fp, frameLength, SEEK_CUR); //向前跳跃到下一个帧头 + memset(&id3v2fh, 0, 10); //清除帧头结构体数据 + fread(&id3v2fh, 10, 1, fp); //重新读取数据 + curDataLength += frameLength + 10; //记录当前所在的ID3标签位置,以便退出循环 + } + + //计算一下当前图片帧的数据长度 + int frameLength = id3v2fh.size[0] * 0x1000000 + id3v2fh.size[1] * 0x10000 + id3v2fh.size[2] * 0x100 + id3v2fh.size[3]; + + /* + 这是ID3v2.3图片帧的结构: + +
+ 头部10个字节的帧头 + + Text encoding $xx + 要跳过一个字节(文字编码) + + MIME type $00 + 跳过(文本 + /0),这里可得到文件格式 + + Picture type $xx + 跳过一个字节(图片类型) + + Description $00 (00) + 跳过(文本 + /0),这里可得到描述信息 + + Picture data + 这是真正的图片数据 + */ + int nonPicDataLength = 0; //非图片数据的长度 + fseek(fp, 1, SEEK_CUR); //信仰之跃 + nonPicDataLength++; + + char tempData[20] = {}; //临时存放数据的空间 + char mimeType[20] = {}; //图片类型 + int mimeTypeLength = 0; //图片类型文本长度 + + fread(&tempData, 20, 1, fp);//取得一小段数据 + fseek(fp, -20, SEEK_CUR); //回到原位 + + strcpy(mimeType, tempData); //复制出一个字符串 + mimeTypeLength = strlen(mimeType) + 1; //测试字符串长度,补上末尾00 + fseek(fp, mimeTypeLength, SEEK_CUR); //跳到此数据之后 + nonPicDataLength += mimeTypeLength; //记录长度 + + fseek(fp, 1, SEEK_CUR); //再一次信仰之跃 + nonPicDataLength++; + + int temp = 0; //记录当前字节数据的变量 + fread(&temp, 1, 1, fp); //读取一个字节 + nonPicDataLength++; //+1 + while (temp) //循环到temp为0 + { + fread(&temp, 1, 1, fp); //如果不是0继续读一字节的数据 + nonPicDataLength++; //计数 + } + //跳过了Description文本,以及末尾的\0 + + //非主流情况检测 + memset(tempData, 0, 20); + fread(&tempData, 8, 1, fp); + fseek(fp, -8, SEEK_CUR); //回到原位 + //判断40次,一位一位跳到文件头 + bool ok = false; //是否正确识别出文件头 + for (int i = 0; i < 40; i++) + { + //校验文件头 + if (verificationPictureFormat(tempData)) + { + ok = true; + break; + } + else + { + //如果校验失败尝试继续向后校验 + fseek(fp, 1, SEEK_CUR); + nonPicDataLength++; + fread(&tempData, 8, 1, fp); + fseek(fp, -8, SEEK_CUR); + } + } + + if (!ok) + { + fclose(fp); + fp = NULL; + freePictureData(); + return false; //无法识别的数据 + } + //-----真正的图片数据----- + picLength = frameLength - nonPicDataLength; //计算图片数据长度 + pPicData = new byte[picLength]; //动态分配图片数据内存空间 + memset(pPicData, 0, picLength); //清空图片数据内存 + fread(pPicData, picLength, 1, fp); //得到图片数据 + //------------------------ + fclose(fp); //操作已完成,关闭文件。 + } + else if (id3v2h.major == 2) + { + //ID3v2.2 + ID3V22FrameHeader id3v2fh; //创建一个ID3v2.2标签帧头结构体 + memset(&id3v2fh, 0, 6); + fread(&id3v2fh, 6, 1, fp); //将数据写到ID3V2.2FrameHeader结构体中 + int curDataLength = 6; //存放当前已经读取的数据大小,刚才已经读入6字节 + while ((strncmp(id3v2fh.FrameId, "PIC", 3) != 0))//如果帧头没有PIC标识符则循环执行 + { + if (curDataLength > tagTotalLength) + { + fclose(fp); + fp = NULL; + return false; //未发现图片数据 + } + //计算帧数据长度 + int frameLength = id3v2fh.size[0] * 0x10000 + id3v2fh.size[1] * 0x100 + id3v2fh.size[2]; + fseek(fp, frameLength, SEEK_CUR); //向前跳跃到下一个帧头 + memset(&id3v2fh, 0, 6); //清除帧头结构体数据 + fread(&id3v2fh, 6, 1, fp); //重新读取数据 + curDataLength += frameLength + 6; //记录当前所在的ID3标签位置,以便退出循环 + } + + int frameLength = id3v2fh.size[0] * 0x10000 + id3v2fh.size[1] * 0x100 + id3v2fh.size[2]; //如果读到了图片帧,计算帧长 + + /* + 数据格式: + + Attached picture "PIC" + Frame size $xx xx xx + Text encoding $xx + Image format $xx xx xx + Picture type $xx + Description $00 (00) + Picture data + */ + + int nonPicDataLength = 0; //非图片数据的长度 + fseek(fp, 1, SEEK_CUR); //信仰之跃 Text encoding + nonPicDataLength++; + + char imageType[4] = {}; + memset(&imageType, 0, 4); + fread(&imageType, 3, 1, fp);//图像格式 + nonPicDataLength += 3; + + fseek(fp, 1, SEEK_CUR); //信仰之跃 Picture type + nonPicDataLength++; + + int temp = 0; //记录当前字节数据的变量 + fread(&temp, 1, 1, fp); //读取一个字节 + nonPicDataLength++; //+1 + while (temp) //循环到temp为0 + { + fread(&temp, 1, 1, fp); //如果不是0继续读一字节的数据 + nonPicDataLength++; //计数 + } + //跳过了Description文本,以及末尾的\0 + + //非主流情况检测 + char tempData[20] = {}; + memset(tempData, 0, 20); + fread(&tempData, 8, 1, fp); + fseek(fp, -8, SEEK_CUR); //回到原位 + //判断40次,一位一位跳到文件头 + bool ok = false; //是否正确识别出文件头 + for (int i = 0; i < 40; i++) + { + //校验文件头 + if (verificationPictureFormat(tempData)) + { + ok = true; + break; + } + else + { + //如果校验失败尝试继续向后校验 + fseek(fp, 1, SEEK_CUR); + nonPicDataLength++; + fread(&tempData, 8, 1, fp); + fseek(fp, -8, SEEK_CUR); + } + } + + if (!ok) + { + fclose(fp); + fp = NULL; + freePictureData(); + return false; //无法识别的数据 + } + //-----真正的图片数据----- + picLength = frameLength - nonPicDataLength; //计算图片数据长度 + pPicData = new byte[picLength]; //动态分配图片数据内存空间 + memset(pPicData, 0, picLength); //清空图片数据内存 + fread(pPicData, picLength, 1, fp); //得到图片数据 + //------------------------ + fclose(fp); //操作已完成,关闭文件。 + } + else + { + //其余不支持的版本 + fclose(fp);//关闭 + fp = NULL; + return false; + } + return true; + } + + //取得图片数据的长度 + int getPictureLength() + { + return picLength; + } + + //取得指向图片数据的指针 + byte *getPictureDataPtr() + { + return pPicData; + } + + //取得图片数据的扩展名(指针) + char *getPictureFormat() + { + return picFormat; + } + + bool writePictureDataToFile(const char *outFilePath) + { + FILE *fp = NULL; + if (picLength > 0) + { + fp = fopen(outFilePath, "wb"); //打开目标文件 + if (fp) //打开成功 + { + fwrite(pPicData, picLength, 1, fp); //写入文件 + fclose(fp); //关闭 + return true; + } + else + { + return false; //文件打开失败 + } + } + else + { + return false; //没有图像数据 + } + } + + //提取图片文件,参数1:输入文件,参数2:输出文件,返回值:是否成功 + bool extractPicture(const char *inFilePath, const char *outFilePath) + { + if (loadPictureData(inFilePath)) //如果取得图片数据成功 + { + if (writePictureDataToFile(outFilePath)) + { + return true; //文件写出成功 + } + else + { + return false; //文件写出失败 + } + } + else + { + return false; //无图片数据 + } + freePictureData(); + } +} +#endif \ No newline at end of file diff --git a/README.md b/README.md index 093f11e..a894c01 100644 --- a/README.md +++ b/README.md @@ -13,3 +13,17 @@ Windows|[Supported Formats In DirectsShow](https://msdn.microsoft.com/en-us/libr macOS|[Media formats supported by QuickTime Player](https://support.apple.com/en-us/HT201290)|Sorry, I don't know... Unix/Linux|depends on [GStreamer](https://gstreamer.freedesktop.org/) plugins which user installed|[GStreamer Plug-ins: gst-plugins-base, gst-plugins-good, gst-plugins-ugly, gst-plugins-bad](https://gstreamer.freedesktop.org/documentation/additional/splitup.html?gi-language=c) +## About License + +Since this is a toy repo, I don't spend much time about the license stuff. Currently this project use some assets and code from [ShadowPlayer](https://github.com/ShadowPower/ShadowPlayer), which have a very interesting license -- do whatever you want but cannot be used as homework -- obviously it's not a so called *free* license. I *may* do some license housecleaning works by replaceing the assets and code implementation when the code become reasonable, and the final codebase may probably released under MIT license. + +Anyway here is a list of file which is in non-free state (with license: do whatever you want but cannot be used as homework): + + - All png images inside `icons` folder. + - FlacPic.h + - ID3v2Pic.h + - seekableslider.{h,cpp} + +Also there are some source code which I copy-paste from Qt codebase, which released under BSD-3-Clause license by the Qt Company: + + - playlistmodel.{h,cpp} diff --git a/mainwindow.cpp b/mainwindow.cpp index 0d271a7..be5af93 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -3,6 +3,9 @@ #include "playlistmodel.h" +#include "ID3v2Pic.h" +#include "FlacPic.h" + #include #include #include @@ -13,6 +16,7 @@ #include #include #include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) @@ -124,6 +128,31 @@ void MainWindow::mouseReleaseEvent(QMouseEvent *event) return QMainWindow::mouseReleaseEvent(event); } +void MainWindow::dragEnterEvent(QDragEnterEvent *e) +{ + // TODO: file/format filter? + + e->acceptProposedAction(); +} + +void MainWindow::dropEvent(QDropEvent *e) +{ + QList urls = e->mimeData()->urls(); + if (urls.isEmpty()) { + return; + } + + QString fileName = urls.first().toLocalFile(); + if (fileName.isEmpty()) { + return; + } + + // TODO: file/format filter? + + createPlaylist(urls); + m_mediaPlayer->play(); +} + void MainWindow::loadFile() { QStringList files = QFileDialog::getOpenFileNames(this, @@ -281,10 +310,32 @@ void MainWindow::initConnections() { connect(m_mediaPlayer, &QMediaPlayer::currentMediaChanged, this, [=](const QMediaContent &media) { #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) - ui->titleLabel->setText(media.canonicalUrl().fileName()); + QUrl fileUrl = media.canonicalUrl(); #else - ui->titleLabel->setText(media.request().url().fileName()); + QUrl fileUrl = media.request().url(); #endif // QT_VERSION < QT_VERSION_CHECK(5, 0, 0) + + ui->titleLabel->setText(fileUrl.fileName()); + if (fileUrl.isLocalFile()) { + using namespace spID3; + using namespace spFLAC; + + QString filePath(fileUrl.toLocalFile()); + + if (filePath.endsWith(".mp3")) { + if (spID3::loadPictureData(filePath.toLocal8Bit().data())) { + QByteArray picData((const char*)spID3::getPictureDataPtr(), spID3::getPictureLength()); + ui->coverLabel->setPixmap(QPixmap::fromImage(QImage::fromData(picData))); + spID3::freePictureData(); + } + } else if (filePath.endsWith(".flac")) { + if (spFLAC::loadPictureData(filePath.toLocal8Bit().data())) { + QByteArray picData((const char*)spFLAC::getPictureDataPtr(), spFLAC::getPictureLength()); + ui->coverLabel->setPixmap(QPixmap::fromImage(QImage::fromData(picData))); + spFLAC::freePictureData(); + } + } + } }); connect(m_mediaPlayer, &QMediaPlayer::positionChanged, this, [=](qint64 pos) { diff --git a/mainwindow.h b/mainwindow.h index 8e7e43d..cfe6b10 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -29,6 +29,8 @@ protected: void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; + void dragEnterEvent(QDragEnterEvent *e) override; + void dropEvent(QDropEvent *e) override; void loadFile(); void centerWindow(); diff --git a/mainwindow.ui b/mainwindow.ui index 4cea614..8bd141a 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -16,6 +16,9 @@ 160 + + true + Pineapple Player @@ -185,6 +188,9 @@ QLabel#coverLabel { AlbumCover + + true +