文章目录

  • 项目背景
  • 可行性分析
  • 需求分析
  • 总体设计
  • 详细设计
  • 项目测试
  • 项目结果
  • 项目源码
项目背景

随着互联网传播技术的普及和网络社交文化的繁荣,传统的文字传播在日常表达上不具有完整的信息,同样一句话在不同的场景或者情绪下会出现不同的效果,使用传统的文字聊天,我们无法通过聊天软件来判别聊天者的表情和动作来理解对方的情绪,因此,表情包在文字表达上可以更好地使对方理解我们此时的情绪,以方便更好的表达情感。
“一言不合就斗图”已经成为了大多数网民所必备的一项技能,一般常用于各式聊天软件,例如微信、QQ等,“斗图”开始于QQ,在进行聊天时,大家发送搞怪图片来表达自己想要表达的意思,后发展在各式社交论坛上,例如贴吧、知乎等;

可行性分析

可行性分析从三方面来分析:
经济可行性:成本比较低
操作可行性:操作简单
技术可行性:利用一些工具和第三方库

需求分析

要生成Gif动态图,我们有两种生成方式:图片生成和视频生成,借助的工具是ffmpeg工具,UI界面布局器,首先介绍一下ffmpeg工具:
ffmpeg是特别强大的专门用于处理音视频的开源库,既可以使用它的API对音视频进行处理,也可以使用它提供的工具,如 ffmpeg, ffplay, ffprobe,来编辑你的音视频文件;ffmpeg由以下几部分组成:
(1)libavcodec: 提供了一系列编码器的实现。
(2)libavformat: 实现在流协议,容器格式及其本IO访问。
(3)libavutil: 包括了hash器,解码器和各利工具函数。
(4)libavfilter: 提供了各种音视频过滤器。
(5)libavdevice: 提供了访问捕获设备和回放设备的接口。
(6)libswresample: 实现了混音和重采样。
(7)libswscale: 实现了色彩转换和缩放工能。
在学习了ffmpeg之后,我们知道ffmpeg是使用命令行的格式,响应Cmd来实现相关的操作,因此我们在实现Gif的制作,主要就是通过Cmd控制台来使用ffmpeg工具,向该工具发送对应的命令来实现各部分操作。

总体设计

本项目共有两种生成Gif的方式:图片生成和视频生成,如图为应该实现的流程:


我们需要依靠Duilib库,它的好处有:
(1)可以基于GDI在窗口上进行重绘,没有其他依赖,没有使用其他的系统调用,能够解决传统MFC界面的一系列问题;
(2)使用XML来描述界面风格与布局,实现了UI和逻辑代码的分离,同时可以实现各种界面效果,例如换肤、透明等;
(3)完全兼容ActiveX控件,也可以和MFC等界面库配合使用;
(4)可广泛用于互联网客户端、工具软件客户端、车载电脑系统等;
Duilib库是基于Win32系统的一套UI库,因此我们先来了解一下Win32相关知识。

  • Win32程序介绍
    1、首先建立一个Win32工程,如图:

    建立工程,出现下图:

    点击下一步,出现下图:

    单击空项目,然后完成,这样一个完整的Win32项目创建完成。
    2、实现代码:
#include <Windows.h>
#include <tchar.h>
//消息回调函数
POINT start;//起点
POINT end;//终点
int state = 0;//刚开始的状态,来控制画什么图形
//用户自己定义:函数的格式必须按照系统的固定格式来定义,
LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{switch (message){case WM_MOUSEMOVE://鼠标移动,要获取起点,在移动期间,触发重绘end.x = LOWORD(lParam);//改变终点end.y = HIWORD(lParam);InvalidateRect(hWnd, NULL, true);//触发重绘break;case WM_LBUTTONDOWN://按鼠标就要触发重绘//MessageBox(NULL, _T("LBTNDOWN"), _T("Test"), IDOK);{start.x = LOWORD(lParam);start.y = HIWORD(lParam);return 0;}case WM_LBUTTONUP://放开鼠标{end.x = LOWORD(lParam);end.y = HIWORD(lParam);HDC hdc = GetDC(hWnd);switch (state){case 1://画直线MoveToEx(hdc, start.x, start.y, NULL);//将光标移到起点的位置LineTo(hdc, end.x, end.y);//画到终点的位置break;case 2://画矩形Rectangle(hdc, start.x, start.y, end.x, end.y);//画矩形break;case 3://画椭圆Ellipse(hdc, start.x, start.y, end.x, end.y);break;}ReleaseDC(hWnd, hdc);//用完了要进行释放start = end;//每次画完后将起点和终点放在同一位置return 0;}case WM_PAINT://重绘{HDC hdc = GetDC(hWnd);switch (state){case 1://画直线MoveToEx(hdc, start.x, start.y, NULL);//将光标移到起点的位置LineTo(hdc, end.x, end.y);//画到终点的位置break;case 2://画矩形Rectangle(hdc, start.x, start.y, end.x, end.y);//画矩形break;case 3://画椭圆Ellipse(hdc, start.x, start.y, end.x, end.y);break;}ReleaseDC(hWnd, hdc);//用完了要进行释放return 0;}case WM_CLOSE:DestroyWindow(hWnd);return 0;case WM_DESTROY:PostQuitMessage(0);return 0;default:return DefWindowProc(hWnd, message, wParam, lParam);}
}int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, intnCmdShow)//hInstance相当于这个应用程序
{//MessageBox(NULL,_T("hell Win32"),_T("Win32"),IDCANCEL);//IDCANCEL/IDOK/等改变按钮//Step1:注册一个窗口类HWND hwnd; //窗口的句柄WNDCLASSEX wc; //窗口类结构wc.cbSize = sizeof(WNDCLASSEX);//当前结构体总共占了多少个字节wc.style = CS_VREDRAW | CS_HREDRAW;//窗口刷新机制wc.lpszMenuName = 0;//是否有菜单wc.lpszClassName = _T("Win32");//标记这个窗口wc.lpfnWndProc = WinProc; //消息回调函数,用户必须提供wc.hInstance = hInstance;wc.hIcon = NULL;wc.hCursor = NULL;wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);wc.cbWndExtra = 0;wc.cbClsExtra = 0;wc.hIconSm = NULL;RegisterClassEx(&wc); //注册窗口//Step2:创建一个窗口hwnd = CreateWindow(_T("Win32"), //窗口的类名,也就是上面我们自定义的窗口类的名字_T("我的第一个Win32程序"), //窗口的标题WS_OVERLAPPEDWINDOW, //窗口style500, //窗口位置x坐标300, //窗口位置y坐标800, //窗口宽度600, //窗口高度NULL, //父窗口句柄NULL,//菜单句柄,没有时设置为NULLhInstance, //实例句柄NULL //创建数据);if (!hwnd){return FALSE;}ShowWindow(hwnd, SW_SHOW); //显示窗口UpdateWindow(hwnd); //刷新//Step3:消息循环MSG msg;while (GetMessage(&msg, NULL, 0, 0)){TranslateMessage(&msg);DispatchMessage(&msg);}return 0;
}

其中Win32程序入口函数为WinMain;
从上面的代码可以看出,Win32程序一般流程为:
(1)设计窗口类:完善窗口类的结构体;将WNDCLASSEX结构体转到定义,为:

其中cbSize表示该结构体的大小;style表示窗口类的样子,通常是CS_HREDRAW |
CS_VREDRAW,表示水平和垂直重绘;IpfnWndProc表示窗口的过程处理函数,这个函数拦截用户需要处理的消息;cbClsExtra和cbWndExtra一般设置为0;HInstance表示窗口实例;hIcon表示窗口类的图标;hCursor表示窗口类的鼠标样式;
(2)注册窗口类:窗口类的名字、提供窗口过程处理函数,使用RegisterClassEx(&wc)函数注册窗口,这样系统才能知道这个窗口的存在;
(3)创建窗口:CreateWindow();
(4)显示窗口:ShowWindow();
(5)更新窗口:Updatewindow();
(6)消息循环:相当于一个死循环:WM_Close(这样窗口就不会一闪而过);
(7)消息响应:在用户自定义的窗口过程处理函数中,用户对自己需要处理的消息进行拦截响应,对不关心的消息采用系统默认的消息响应函数DefWindowProc()处理即可。
接下来介绍Win32的消息循环;

  • 消息机制
    1、消息概念:是系统内设的一种数据结构;如图:

    hwnd是窗口的句柄,这个参数将决定由哪个窗口过程函数对消息进行处理;message是一个消息常量,用来表示消息的类型; wParam表示32 位的附加信息,具体表示什么内容,要视消息的类型而定;lParam表示32 位的附加信息,具体表示什么内容,要视消息的类型而定;time是消息发送的时间;pt表示消息发送时鼠标所在的位置
    2、消息分为系统定义消息和用户自定义消息
    系统定义消息分为:窗口消息、命令消息、控件通知消息。
    (1)窗口消息
    与窗口的内部运作有关的消息,如创建窗口,绘制窗口,销毁窗口等 ,如:WM_CREATE, WM_PAINT,WM_MOUSEMOVE等。
    (2) 命令消息
    当用户从菜单选中一个命令项目、按下一个快捷键、点击工具栏上的一个按钮或者点击控件都将发送
    WM_COMMAND命令消息。通过消息结构中的wParam和lParam成员就能清楚得知道消息的来源。
    LOWORD(wParam):代表菜单ID、或控件ID,或快捷键ID; HIWORD(wParam):表示通知码,当消息是从菜单发出时,则这个值为0,当消息是从快捷键发出时,这个值为1,当消息是从控件发出时,这个值为通知码,比如按钮的通知码:BN_CLICKED, BN_DBLCLK等;
    lParam:当消息从菜单和快捷键发出时,这个值为0,当从控件发出时,为控件的句柄。
    (3) 控件通知消息
    随着控件的种类越来越多,越来越复杂(如列表控件、树控件等),仅仅将wParam,lParam将视为一个32位无符号整数,已经装不下太多信息了。 为了给父窗口发送更多的信息,微软定义了一个新的WM_NOTIFY消息来扩展WM_COMMAND消息。 WM_NOTIFY消息仍然使用MSG消息结构,只是此时wParam为控件ID,lParam为一个NMHDR指针,不同的控件可以按照规则对NMHDR进行扩充,因此WM_NOTIFY消息传送的信息量可以相当的大。
    缺陷:在Windows上进行图形界面开发,效率低,因此我们需要引进Duilib库
详细设计

1、进行环境配置:vs2013将Duilib库包含进来
(1)Duilib环境搭建:
先对Duilib库编译(Duilib库自行下载),将生成的静态库lib和生成的dll文件包含到最外层的Debug目录下,与.exe文件一层,将库文件Duilib放在与.cpp文件一层的目录下,如图:


然后对环境进行配置,如图点开项目属性,在C/C++栏中的附加包含目录栏中选择Duilib所在的目录:

然后在链接器栏的附加库目录选择lib文件所在的目录,如图:

然后在代码中加入:

#include "UIlib.h"
using namespace DuiLib;
#pragma comment(lib, "DuiLib_ud.lib")

之后我们就可以使用Duilib库来进行窗口的创建响应等操作了;
(2)Duilib库的使用
1> 定义类CDuiFramWnd继承 CWindowWnd类,在该类中实现:
virtual LPCTSTR GetWindowClassName():返回窗口类的名字(CWindowWnd:Duilib自己封装的关于窗口类的相关操作:create(…):注册窗口和创建窗口,显示窗口,更新窗口;ShowModal():消息循环)
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam):子类如果需要处理系统消息(Windows自己维护的消息)时,需要进行重写uMsg:获取到的消息ID—>区分捕获到的是什么类型的消息
2> 另外CDuiFramWnd继承自INotifyUI类(duilib自己定义的类—>抽象类),按钮创建成功后,添加按钮控件消息响应到duilib的消息循环中;重写INotifyUI类的Notify纯虚函数,在该函数中用户捕获其想要处理的消息,进行自己想要的操作即可。

class INotifyUI
{virtual void Notify(TNotifyUI& msg)=0;
};

如果需要拦截duilib自己维护的消息时,只需要在子类中重写Notify即可;

typedef struct tagTNotifyUI
{CDuiString sType;//消息的类型---"click"鼠标点击"windowinit"CDuiString sVirtualWnd;CControlUI* pSender;//消息是由哪个空间触发的DWORD dwTimestamp;POINT ptMouse;WPARAM wParam;LPARAM lParam;
} TNotifyUI;

3> Duilib其实并没有区分标题栏和客户区,它的实现方法是屏蔽了系统自带的标题栏,用客户区来模拟标题栏,所以想怎么画就怎么画,非常方便。 在HandleMessage函数里屏蔽以下三个消息即可 WM_NCACTIVATE、WM_NCCALCSIZE、WM_NCPAINT;
实现代码:
实现代码:

#include "UIlib.h"
using namespace DuiLib;
#pragma comment(lib, "DuiLib_ud.lib")
class CDuiFramWnd : public CWindowWnd,public INotifyUI
{
public:// CWindowWnd类的纯虚函数,在该函数中必须返回用户所定义窗口的类名称,注册窗口时需要用到virtual LPCTSTR GetWindowClassName() const{return _T("DuiFramWnd");}virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)//处理系统消息{if (WM_CREATE == uMsg){初始化绘画管理器//m_PaintManager.Init(m_hWnd);//m_hWnd:基类中的句柄在窗口创建期间创建一个按钮//CControlUI* pBTN = new CButtonUI;//定义按钮//pBTN->SetText(_T("hello"));//设置窗口按钮文本//pBTN->SetBkColor(0xFF00FF00);//设置窗口按钮颜色//m_PaintManager.AttachDialog(pBTN);//将按钮关联到绘制管理器上//m_PaintManager.AddNotifier(this);//将按钮增加到消息循环中m_PaintManager.Init(m_hWnd);CDialogBuilder builder;// duilib.xml需要放到exe目录下CControlUI* pRoot = builder.Create(_T("111.xml"), (UINT)0, NULL, &m_PaintManager);//要设置路径m_PaintManager.AttachDialog(pRoot);m_PaintManager.AddNotifier(this);return 0;}else if (uMsg == WM_NCACTIVATE){if (!::IsIconic(m_hWnd)){return (wParam == 0) ? TRUE : FALSE;}}else if (uMsg == WM_NCCALCSIZE){return 0;}else if (uMsg == WM_NCPAINT){return 0;}//拦截会话相关消息LRESULT lRse = 0;if (m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRse)){return lRse;}//其他消息return __super::HandleMessage(uMsg, wParam, lParam);}virtual void Notify(TNotifyUI& msg){//响应按钮单击消息if (msg.sType == _T("click"))//注意这里不能交换常量和sType{MessageBox(m_hWnd, _T("按钮单击"), _T("Test"), IDOK);}}
private:CPaintManagerUI m_PaintManager;
};int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, intnCmdShow)
{//对应111.xml文件,因为要找到路径CPaintManagerUI::SetInstance(hInstance);// 设置资源的默认路径(此处设置为和exe在同一目录)CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath());CDuiFramWnd framWnd;// Cashier即在窗口右上角显式的名字// UI_WNDSTYLE_FRAME: duilib封装的宏,代表窗口可视,具有标题栏,最大化最小化,关闭功能等// WS_EX_WINDOWEDGE: Win32的窗口风格,带有边框framWnd.Create(NULL, _T("DulibTest"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);//显示窗口,激活消息循环framWnd.ShowModal();return 0;
}
  • 具体项目实现
    1、我这里实现的是能够使用这个窗口来响应对照片或者视频进行gif动态图的制作,因此这时需要用到ffmpeg工具(它提供了音视频的编码,解码,转码,封装,解封装,流,滤镜,播放等功能),此时就需要用到ffmpeg相关命令,我们先将所需要的图片和外挂字幕视频资源以及ffmpeg工具放在Win32工程最外层的Debug目录下,我们可以在本机的cmd控制台上执行相关命令先来完成gif动态图的制作,相关命令如下:
    使用图片生成gif动态图

ffmpeg -r 3 -i .\Picture %d.jpg output.gif -y

其中-r后跟上数字表示每一次显示多少帧,-i表示输入,.\Picture %d.jpg表示图片资源路径,其中%d表示通配以数字开头的照片,output.gif表示生成的gif名字,-y表示如果路径下存在与输出文件相同的文件名,则覆盖
使用视频生成gif动态图

1、从原视频中截取所需要的片段:ffmpeg -i input.mkv -vcodec copy -acodec copy -ss 00:40:07 -to 00:40:28 11.mkv -y

-vcodec copy -acodec copy表示将视频和音频流拷贝过来,-ss … -to …表示截取视频的长度,11.mkv表示生成的片段;

2、从视频中提取字幕:ffmpeg -i 11.mkv input.srt -y

input.srt表示生成的字幕;

3、编辑字幕

4、从截取的片段中只抽离视频(不要字幕和音频),即提取视频裸流:ffmpeg -i 11.mkv -vcodec copy -an -sn 22.mkv -y

其中-an表示不要音频,-sn表示不要字幕,22.mkv是生成的视频裸流;

5、将修改后的srt字幕内嵌到视频裸流中,即烧录成一个整天:ffmpeg -i 22.mkv -vf subtitles = input.srt 33.mkv -y

6、生成动态图:ffmpeg -i 33.mkv -vf scale = iw/2:ih/2 -f gif

表示按照等比例的方式生成gif;
也可以使用(ffmpeg -i 33.mkv -s 200*200 -v 15 output.gif -y)其中-s控制大小;
这些命令是针对cmd控制台的,所以必须要使用ShellExecuteEx函数来给控制台发命令,我们可以看一下这个函数的用法:

//启动:SHELLEXECUTEINFO ShExecInfo;ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS ;ShExecInfo.hwnd = NULL;ShExecInfo.lpVerb = NULL;ShExecInfo.lpFile = "xxx.exe"; //can be a file as wellShExecInfo.lpParameters = "";ShExecInfo.lpDirectory = NULL;ShExecInfo.nShow = SW_SHOW;ShExecInfo.hInstApp = NULL;ShellExecuteEx(&ShExecInfo);
//关闭:if( ShExecInfo.hProcess != NULL){TerminateProcess(ShExecInfo.hProcess,0);ShExecInfo.hProcess = NULL;}

2、使用Duilib中的UI界面布局器进行窗口设计,我们使用UI布局器来对各个控件(按钮、编辑框、下拉框等)进行绘制,此时就会生成一个xml文件,但是由于UI界面布局器自身存在缺陷,例如:
bug—>在使用时记得及时备份和对于控件支持的不是非常全面;
因此我们在使用时必须及时保存,对于不支持的控件我们可以查询资料对xml文件进行编辑补充,这里可以推荐相关链接:
https://www.cnblogs.com/Alberl/p/3354459.html
如图:

选择图片我们就不需要截取编辑框和视频加载路径的控件,诸如截取、提取SRT、写入SRT、提取视频、烧录的按钮都不用实现,因为图片只需要一组ffmpeg命令直接生成Gif就可以,在点击生成Gif按钮后,就可以生成Gif了;选择视频我们就需要一步步来进行实现,点击按钮即可;
3、制作Gif各个部分代码的实现
(1)功能设置,类CDuiFramWnd继承WindowImplBase这个类,因为Duilib已经对常用的操作进行了很好的封装,正常使用时不需要按照之前的方式实现,只需要让用户实现的窗口类继承自Duilib封装的:WindowImplBase 类即可,该类是一个duilib的基础框架类,封装了常用操作,以方便大家使用。 它是以XML作为界面描述的,所以用它的时候,我们必须将界面描述写到XML里。即通过xml文件描述窗口—窗口创建
重写这3个纯虚函数:
virtual CDuiString GetSkinFolder();
virtual CDuiString GetSkinFile();
virtual LPCTSTR GetWindowClassName(void)const;

class CDuiFramWnd : public WindowImplBase
{
protected:virtual CDuiString GetSkinFolder(){return _T("");//这里不用给xml文件的路径,因为在主函数中已经给过了}virtual CDuiString GetSkinFile(){return _T("gifMake.xml");//一定要与目录放的xml文件文件名称相同}virtual LPCTSTR GetWindowClassName(void)const{return _T("GIFMakeWnd");}

然后进行消息处理:
1、Windows系统消息—HandleMessage(这个在CWindowWnd类中)
2、duilib消息—duilib所维护的控件所产生的消息:Notify()(这个在INotifyUI类中);

 virtual void Notify(TNotifyUI& msg)//响应{CDuiString strName = msg.pSender->GetName();//获取控件的名字if (msg.sType == _T("click"))//如果是鼠标点击{if (strName == _T("btn_close"))//如果是关闭按钮{Close();}else if (strName == _T("btn_min"))//如果是最小化按钮{//发送最小化消息SendMessage(WM_SYSCOMMAND, SC_MINIMIZE, 0);}else if (strName == _T("btn_load")){LoadFile();}else if (strName == _T("btn_cut"))//截取视频{Cutview();}else if (strName == _T("btn_get_srt"))//提取字幕{GetSRTFile();//提取LoadSRT();//加载}else if (strName == _T("btn_commit"))//提交按钮{CEditUI* pEdit = (CEditUI*)m_PaintManager.FindControl(_T("edit_word"));CDuiString strWord = pEdit->GetText();//获取这个edit对应的文本//将该文本写回到list中,此时已经在edit中编辑过了CListUI* pList = (CListUI*)m_PaintManager.FindControl(_T("List_srt"));//先获取list控件CListTextElementUI* pListItem = (CListTextElementUI*)pList->GetItemAt(pList->GetCurSel());//将当前list的这一行内容选中pListItem->SetText(1, strWord);//下标为1}else if (strName == _T("btn_write_srt"))//写入字幕{WriteSRT();}else if (strName == _T("btn_view"))//提取视频{GenerateView();}else if (strName == _T("btn_bron"))//烧录{BornSRTtoView();}else if (strName == _T("btn_generate"))//生成gif{//根据CComboBox控件的name拿到该控件,也就是绘画管理器中来进行管理的CComboBoxUI* pCombo = (CComboBoxUI*)m_PaintManager.FindControl(_T("combo_select"));if (0 == pCombo->GetCurSel())//如果当前选中是0{GenerateGifWithPic();//使用图片生成}else{GenerateGifWithView();//使用视频生成}}}

(2)设置控件,如果我们选择的是图片生成,就需要让视频生成的相关控件无效

//在窗口初始化期间,由于默认的是图片生成,我们需要让视频生成的相关控件无效else if (msg.sType == _T("windowinit")){SetControlEnable(false);}else if (msg.sType == _T("itemselect"))//如果下拉框Combo是选择改变了{if (strName == _T("List_srt"))//表示选择改变,将改变后的内容放在右侧的edit中即可{//获取list控件CListUI* pList = (CListUI*)m_PaintManager.FindControl(_T("List_srt"));//拿出list中某一项的内容CListTextElementUI* pListItem = (CListTextElementUI*)pList->GetItemAt(pList->GetCurSel());//将list选中行中的对应文本信息增加到edit中CEditUI* pEdit = (CEditUI*)m_PaintManager.FindControl(_T("edit_word"));pEdit->SetText(pListItem->GetText(1));//获取第一项,并且设置进edit框中}if (strName == _T("combo_select")){CComboBoxUI* pComboUI = (CComboBoxUI*)m_PaintManager.FindControl(_T("combo_select"));if (0 == pComboUI->GetCurSel()){//选择图片方式生成,让以下控件无效SetControlEnable(false);}else//选择视频方式生成,让以下控件有效{SetControlEnable(true);}}}}

(3)各个函数的实现
设置控件是有效还是无效

void SetControlEnable(bool IsValid)//将一些控件设置为有效或者无效{((CEditUI*)m_PaintManager.FindControl(_T("edit_start")))->SetEnabled(IsValid);//start编辑框设置((CEditUI*)m_PaintManager.FindControl(_T("edit_end")))->SetEnabled(IsValid);//end编辑框设置((CButtonUI*)m_PaintManager.FindControl(_T("btn_cut")))->SetEnabled(IsValid);//截取按钮设置((CButtonUI*)m_PaintManager.FindControl(_T("btn_get_srt")))->SetEnabled(IsValid);//提取SRT按钮设置((CButtonUI*)m_PaintManager.FindControl(_T("btn_write_srt")))->SetEnabled(IsValid);//写入SRT按钮设置((CButtonUI*)m_PaintManager.FindControl(_T("btn_view")))->SetEnabled(IsValid);//提取视频按钮设置((CButtonUI*)m_PaintManager.FindControl(_T("btn_bron")))->SetEnabled(IsValid);//烧录按钮设置}

使用图片生成Gif:

//1、使用图片生成gif动态图void GenerateGifWithPic(){CDuiString strPath = CPaintManagerUI::GetInstancePath();//获取工程的目录,刚好与exe文件在同一个目录strPath += _T("ffmpeg\\");//1.构造命令CDuiString strCMD;strCMD += _T("/c ");//构造命令期间第一条必须是'/c',要加上/c参数strCMD += strPath;strCMD += _T("ffmpeg -r 3 -i ");strCMD += strPath;strCMD += _T(".\\Picture\\%d.jpg ");//这里是图片的路径,注意要进行转义字符的转义strCMD += strPath;strCMD += _T("output.gif -y");//生成动态图,-y表示如果该目录下存在output.gif,就覆盖//2.给cmd发命令SendCmd(strCMD);}

使用视频生成Gif,分别实现:加载原视频路径、截取视频、提取字幕、写入字幕、提取视频裸流、烧录、生成Gif等函数

//2、使用视频生成gif动态图//(1)截取所需要的片段void Cutview(){CDuiString strPath = CPaintManagerUI::GetInstancePath();//获取路径strPath += _T("ffmpeg\\");//此时我们有两种方法,因为加载按钮响应后,我们可以从加载编辑框中获取路径CDuiString strViewPath = ((CEditUI*)m_PaintManager.FindControl(_T("edit_path")))->GetText();//获取到编辑框中内容的路径,也就是拿到了视频的完整路径//1.构造命令CDuiString strCMD;strCMD += _T("/c ");//构造命令期间第一条必须是'/c'strCMD += strPath;strCMD += _T("ffmpeg -i ");//要加上\c参数//优先通过界面中编辑框来加载视频路径if (!strViewPath.IsEmpty()){strCMD += strViewPath;}else//再到默认路径下获取文件{strCMD += strPath;strCMD += _T("input.mkv ");//视频文件的路径}strCMD += _T("-vcodec copy -acodec copy ");strCMD += _T("-ss ");//获取起始时间和结尾时间CDuiString strStartTime = ((CEditUI*)m_PaintManager.FindControl(_T("edit_start")))->GetText();if (!IsValidTime(strStartTime)){MessageBox(NULL, _T("起始时间有误"), _T("MakeGif"), IDOK);}CDuiString strEndTime = ((CEditUI*)m_PaintManager.FindControl(_T("edit_end")))->GetText();if (!IsValidTime(strEndTime)){MessageBox(NULL, _T("终止时间有误"), _T("MakeGif"), IDOK);}strCMD += strStartTime;strCMD += _T(" -to ");strCMD += strEndTime;strCMD += _T(" ");//输出文件的路径strCMD += strPath;strCMD += _T("11.mkv -y");//2.向cmd发送命令SendCmd(strCMD);}void LoadFile()//加载视频文件的路径{OPENFILENAME ofn;memset(&ofn, 0, sizeof(OPENFILENAME));//设置参数TCHAR strPath[MAX_PATH] = { 0 };//MAX_PATH--->260ofn.lStructSize = sizeof(OPENFILENAME);ofn.lpstrFile = strPath;ofn.nMaxFile = sizeof(strPath);ofn.lpstrFilter = _T("All(*.*)\0 *.*\0mkv(*.mkv)\0 *.mkv\0");//使用这个过滤串,我们这里是过滤出mkv文件ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;if (GetOpenFileName(&ofn)){//将文件的路径设置到edit((CEditUI*)m_PaintManager.FindControl(_T("edit_path")))->SetText(strPath);//将路径设置进编辑框中}}//(2)从截取的视频中提取字幕(必须是外挂字幕)void GetSRTFile(){CDuiString strPath = CPaintManagerUI::GetInstancePath();//获取路径strPath += _T("ffmpeg\\");//1.构造命令CDuiString strCMD;strCMD += _T("/c ");//构造命令期间第一条必须是'/c'strCMD += strPath;strCMD += _T("ffmpeg -i ");//要加上\c参数strCMD += strPath;strCMD += _T("11.mkv ");//视频文件的路径strCMD += strPath;strCMD += _T("input.srt -y");//输出的字幕文件//2.向cmd发送命令SendCmd(strCMD);}//(3)编辑字幕,在窗口中编辑void LoadSRT(){//将srt格式的字幕文件,加载到界面中的list控件CDuiString strPath = CPaintManagerUI::GetInstancePath();//得到文件的路径strPath += _T("ffmpeg\\input.srt");std::ifstream fIn(strPath.GetData());//获取字符串类型的文件char strSRTCon[512] = { 0 };CListUI* pList = (CListUI*)m_PaintManager.FindControl(_T("List_srt"));//控件都是通过绘画管理器画出来的,拿到list控件//给list中添加每一行pList->RemoveAll();//第二次获取字幕时要清掉第一次获取的字幕while (!fIn.eof())//文件指针有没有在文件结尾{//读取字幕序号fIn.getline(strSRTCon, 512);//给出list中的文本元素CListTextElementUI* pListItem = new CListTextElementUI;pList->Add(pListItem);//读取时间轴fIn.getline(strSRTCon, 512);pListItem->SetText(0, UTF8ToUniCode(strSRTCon));//从0开始,将文本设置进去,但是此时strSRTCon是LPCTSTR类型的,//win32项目是基于Unicode的,不是ASCII形式的,因此要进行转化(UTF-8--->Unicode)//读取字幕fIn.getline(strSRTCon, 512);pListItem->SetText(1, UTF8ToUniCode(strSRTCon));//读取空行fIn.getline(strSRTCon, 512);}fIn.close();}void WriteSRT(){// 获取SRT文件的路径CDuiString strPath = CPaintManagerUI::GetInstancePath();strPath += _T("ffmpeg\\input.srt");std::ofstream fOut(strPath.GetData());//写到fout中// 1. 从List控件中获取文本内容CListUI* pList = (CListUI*)m_PaintManager.FindControl(_T("List_srt"));int szCount = pList->GetCount();//得到总共的行for (int i = 0; i < szCount; ++i){CListTextElementUI* pListItem = (CListTextElementUI*)pList->GetItemAt(i);// 序号CDuiString strNo;strNo.Format(_T("%d"), i + 1);//因为srt文件标号是从1开始的,而i是从0开始的,所以要+1// 时间轴CDuiString strTime = pListItem->GetText(0);// 文本内容CDuiString strWord = pListItem->GetText(1);// 2. 将获取到的内容写会到srt文件中string strNewLine = Unicode2UTF8(_T("\n"));// 写行号string itemNo = Unicode2UTF8(strNo);fOut.write(itemNo.c_str(), itemNo.size());fOut.write(strNewLine.c_str(), strNewLine.size());// 写时间轴string itemTime = Unicode2UTF8(strTime);fOut.write(itemTime.c_str(), itemTime.size());fOut.write(strNewLine.c_str(), strNewLine.size());// 写文本string itemWord = Unicode2UTF8(strWord);fOut.write(itemWord.c_str(), itemWord.size());fOut.write(strNewLine.c_str(), strNewLine.size());// 字幕和字幕之间都有个换行 fOut.write(strNewLine.c_str(), strNewLine.size());}fOut.close();}//(4)从截取的片段中提取视频裸流void GenerateView(){CDuiString strPath = CPaintManagerUI::GetInstancePath();//获取路径strPath += _T("ffmpeg\\");//1.构造命令CDuiString strCMD;strCMD += _T("/c ");//构造命令期间第一条必须是'/c'strCMD += strPath;strCMD += _T("ffmpeg -i ");//要加上\c参数strCMD += strPath;strCMD += _T("11.mkv -vcodec copy -an -sn ");strCMD += strPath;strCMD += _T("22.mkv -y");SendCmd(strCMD);}void BornSRTtoView(){//ffmpeg -i 22.mkv -vf subtitles=input.srt 33.mkv -yCDuiString strCMD;strCMD += _T("/c ");//构造命令期间第一条必须是'/c'strCMD += _T("cd ");strCMD += CPaintManagerUI::GetInstancePath() + _T("ffmpeg");//获取路径strCMD += _T(" & ");//构造命令strCMD += _T("ffmpeg -i 22.mkv -vf subtitles=input.srt 33.mkv -y");SendCmd(strCMD);}void GenerateGifWithView(){CDuiString strPath = CPaintManagerUI::GetInstancePath();//获取路径strPath += _T("ffmpeg\\");//1.构造命令CDuiString strCMD;strCMD += _T("/c ");//构造命令期间第一条必须是'/c'strCMD += strPath;strCMD += _T("ffmpeg -i ");//要加上\c参数strCMD += strPath;strCMD += _T("33.mkv -vf scale=iw/2:ih/2 -f gif ");strCMD += strPath;strCMD += _T("output.gif -y");SendCmd(strCMD);}

响应Cmd:

    void SendCmd(const CDuiString& strCMD)//向控制台发命令{//1.初始化结构体SHELLEXECUTEINFO strSEInfo;memset(&strSEInfo, 0, sizeof(SHELLEXECUTEINFO));strSEInfo.cbSize = sizeof(SHELLEXECUTEINFO);strSEInfo.fMask = SEE_MASK_NOCLOSEPROCESS;strSEInfo.lpFile = _T("C:\\Windows\\System32\\cmd.exe");//windows命令行cmd所在的路径,就是本机的cmd所在的路径,一定要给对strSEInfo.lpParameters = strCMD;//打开程序的参数strSEInfo.nShow = SW_SHOW;//SW_HIDE:隐藏//调用命令行窗口,给命令行发消息,//在该函数中,会新创建一个进程,来负责调用命令行窗口执行命令ShellExecuteEx(&strSEInfo);//等待命令响应完成WaitForSingleObject(strSEInfo.hProcess, INFINITE);MessageBox(m_hWnd, _T("命令操作完成"), _T("MakeGif"), IDOK);}

时间判断:

bool IsValidTime(CDuiString strTime)//判断给的时间是否有效{//"00:40:07"---时间格式if (strTime.GetLength() != 8){return false;}for (int i = 0; i < strTime.GetLength(); ++i){if (strTime[i] == ':')continue;if (!(strTime[i] >= '0' && strTime[i] <= '9'))//不是数字{return false;}}return true;}

UTF-8与Unicode格式的相互转换:

    CDuiString UTF8ToUniCode(const char* str)//UTF-8--->Unicode {//第一次调用:获取转化之后的目标串的长度int szLen = ::MultiByteToWideChar(CP_UTF8, 0, str, strlen(str), NULL, 0);wchar_t* pContent = new wchar_t[szLen + 1];//为目标串申请空间,要存储\0//第二次调用:进行真正的转化::MultiByteToWideChar(CP_UTF8, NULL, str, strlen(str), pContent, szLen);pContent[szLen] = '\0';CDuiString s(pContent);delete[]pContent;return s;}string Unicode2UTF8(CDuiString str){int len = WideCharToMultiByte(CP_UTF8, 0, str.GetData(), -1, NULL, 0, NULL, NULL);CHAR *szUtf8 = new CHAR[len + 1]{0};::WideCharToMultiByte(CP_UTF8, 0, str.GetData(), -1, (LPSTR)szUtf8, len, NULL, NULL);string s(szUtf8);delete[] szUtf8;return s;}
项目测试

使用单元测试,采用白盒测试方法;
(1)加载文件测试:按钮中实现加载按钮,确认是否能够获取到文件的路径;
(2)Cmd命令发送测试:打开控制台Cmd,验证能否使用ffmpeg命令行生成Gif成功;
(3)图片生成Gif测试:按下生成Gif按钮,在所给文件路径中查看是否生成想要的Gif;
(4)视频截取测试:按下按钮,注意时间的判断,输入错误时间验证是否是有效时间;
(5)视频生成Gif测试:执行每步操作,测试想要的Gif是否生成;
总体测试:全部操作一遍,验证是否与想要的功能对应。

项目结果

如图:
主界面:

按相关按钮即可进行生成

项目源码

https://github.com/wangbiy/project/tree/master/Win32_2019_12_5_Project/Win32_2019_12_5_Project

项目:“表情包”制作---利用ffmpeg制作Gif动态图相关推荐

  1. 如何快速生成gif动图表情包?教你制作gif表情包图片

    Gif动图作为当代网络较为流行的一种图片格式,经常会在公众号推文中.聊天软件聊天中见到其身影.那么,gif动图表情包是如何制作的呢?通常情况下,制作gif动画图片需要专业的工具,而大家可以使用[GIF ...

  2. 王境泽表情包出处,怎么制作GIF动态图?

    自变形记的播出,王境泽的真香定律就开始走红网络,看过节目的应该都知道原委,自从王境泽的表情包被爆出,很长一段时间这个表情包成为朋友之间聊天必用的GIF,那类似的表情包出处是哪里呢,动态表情包如何制作呢 ...

  3. 小程序利用canvas实现波浪动态图,原生canvas的部分限制

    小程序利用canvas实现波浪动态图 小程序的canvas与该动态图的问题 首先上效果图片 下面直接上代码 小程序的canvas与该动态图的问题 小程序的canvas与html的canvas实现是完全 ...

  4. 项目:私“图”定制——利用ffmpeg制作Gif

    目录: 项目背景 可行性方面 需求分析 详细设计 测试 维护 项目效果图展示 完整代码 项目背景: 文字信息时代,传统的文字聊天方式已不能满足大众的需求,很多时候文字不能表达自己的想法,或者沟通技巧的 ...

  5. python制作会动的表情包_利用python图片生成,需10几行代码,生成的动态表情包(小黄鸭)...

    小编找到了10几行的,好玩的动态表情包生成的代码.分享分享! 这个程序怎么做呢?不要急,来听我给大家分析分析. 工具:程序环境 语言:Python 3.6 编辑器:Pycharm 程序步骤 实现字符动 ...

  6. 利用FFmpeg制作视频序列

    前言 本文介绍了利用FFmpeg进行视频测试序列制作的方法.所谓测试序列就是将若干个不同场景的视频片段,按照不同的分辨率.码率.编码方法等进行处理,然后拼接成一条长视频,供测试者打分.一般包括以下几个 ...

  7. 表情包的gif怎么制作?一招教你快速做gif表情

    Gif表情包是当下网友们聊天必备的一种情感表达方式,利用一张有动态效果的图片就可以表达出自己的心情和想法,十分有趣.那么,大家一定很好奇自己应该如何制作gif动图表情包吧!很简单,使用gif制作(ht ...

  8. Gif表情包如何用视频制作?教你一键快速制作gif表情包

    我身边有不少小伙伴都会把网上下载的电影.电视剧或是短视频制作成好玩有趣的gif表情包来使用.一般情况下使用[GIF中文网]的视频转gif(https://www.gif.cn/)功能,只需要上传mp4 ...

  9. 表情包APP小程序制作开发功能有哪些?

    表情包小程序在实际的开发过程中所具备的功能都是至关重要的,功能完善好操作才能更受用户青睐.表情包小程序制作开发功能包括但不仅限于以下几点:        1.热门表情包推荐.表情包制作小程序为用户推荐 ...

最新文章

  1. 西工大计算机专业课考什么,我的西北工业大学计算机考研之路总结
  2. 生了娃照样能打,两位母亲温网会师
  3. Spring Boot:使用Rabbit MQ消息队列
  4. 用Python学分析 - 单因素方差分析
  5. 【渝粤题库】陕西师范大学201571金融法作业(专升本)
  6. 记一次解决问题的掉坑过程
  7. win8消费者预览版Chrome无法设置默认浏览器
  8. ios8升级ios12教程_iOS SpriteKit教程
  9. ymPrompt.js消息提示组件 详解 .
  10. mysql死锁自动处理方法_19. 死锁的产生和 MySQL 自动处理方式
  11. Shopee平台很火爆,但是产品价格设置的不合理,努力都是白费的
  12. VGA\HDMI转换芯片——MS9288C
  13. 基于阈值处理的图像分割算法!
  14. 三个Python自动化测试高效工具的使用总结
  15. S60v5 编程环境的搭建
  16. 七夕如何向心爱的ta表白,看这里
  17. 战神引挚手游数据库解析mysql/mir
  18. 详解FLETA的Proof-of-Formulation共识算法
  19. 实现MASW分析以及速度反演的工具包——MASWaves
  20. 03.尚硅谷网课3-字符实体与语义标签

热门文章

  1. 第四章Java核心类库_多线程
  2. 比基尼美女_人像摄影吧主题 - 云寻觅云贴吧
  3. mysql怎么添加查询时间限制_mysql如何限制sql查询时间,
  4. vue中v-for使用key
  5. 张俊林:万字长文讲述由ChatGPT反思大语言模型的技术精要
  6. 『每日AI』王劲离职景驰科技丨无人驾驶领域的领跑者还是叛逃者?
  7. 达摩达兰论估价pdf_《故事与估值:商业故事的价值》pdf文字版下载
  8. tabbar图片位置大小修改
  9. Lenovo Thinkpad BIOS中开启VT虚拟化后无法生效?
  10. WiFi功耗管理(三)(U-APSD)