一、编译环境
VS2013
二、项目功能
- 可以将文件夹中的图片生成GIF动态图;
- 可以将指定的视频文件,修改其中的字幕,然后生成GIF动态图;
三、项目总体流程
- 通过DuiLib库在VS2013中生成一个可视化界面;
- 创建一个XML文件,通过修改XML文件对软件界面进行修改,并且添加相应的控件;
- 逐步完成各个控件的功能。
四、具体实现过程
1. 创建UI界面
在VS2013中,我们创建工程时可以选择创建一个Win32的工程,通过完成一个Win32程序,就可以实现一个可视化界面。
一个Win32应用程序主要包括以下几部分:
- 应用程序入口:Win32程序不同于我们平时创建的空项目,需要以WinMain函数作为程序入口。
- 注册窗口类:在创建窗口之前需要对窗口的一些属性进行设置,如窗口的大小、颜色、边框等等,需要使用函数RegisterClass()。
- 创建窗口:根据前面设置好的参数,使用函数CreateWindow()创建窗口。
- 显示窗口:使用ShowWindow()显示窗口。
- 更新窗口:使用UpdateWindow()更新窗口。
- 消息循环:消息可分为三种,有窗口消息和命令消息以及通知消息,窗口消息时窗口自身产生的消息,比如当窗口创建成功时会产生一个消息,我们可以通过捕获这个消息,然后在这个情况下进行一些操作,这样就相当于设置成了创建窗口就会有这些操作。而命令消息就是当用户对应用程序进行操作时产生的消息,可以通过消息循环中定义相对应处理操作来完成程序响应。通知消息是为了让父窗口能够接受更多的消息,而新添加的WM_NOTIFY消息,这里面可以通过控件名称来完成相应的处理,十分的方便。
但是直接使用VS2013中的Win32构建图形化界面并不好,因为它的窗口时采用相互叠加的方式,并不美观,其次更是因为生成程序体积太大。所以我们可以借用别的C++开发库---DuiLib库,来更加轻松的完成界面设计。DuiLib库是一套基于Win32上的的一套UI库,可以很好的与MFC等界面库配合使用,同时它可以通过修改XML文件来对界面完成各种个性化设置。
在使用DuiLib库之前需要对其进行环境配置,具体可以网上找一下,也不复杂。配置好之后,需要在项目中添加头文件:#include"DuiLib\UIlib.h" 以及命名空间使用“DuiLib”。
(1)下面先实现一个最简单的界面
//创建一个用户窗口类CDuiFrameWnd,CWindowWnd类是Duilib库中封装好的一个窗口基类,它已经将注册
//窗口、创建窗口、显示窗口等操作都封装好了。而INotifyUI就是前面所说的有关通知消息处理的类,通
//果重写Notify函数,在其中可以通过控件名称完成响应的控件响应操作。
class CDuiFrameWnd : public CWindowWnd, public INotifyUI
{
public:
virtual LPCTSTR GetWindowClassName() const { return _T("DUIMainFrame"); }
virtual void Notify(TNotifyUI& msg) {}
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT lRes = 0;
if( uMsg == WM_CREATE ) //当创建窗口时捕获创建窗口信息,进行操作
{
//CControlUI 是duilib中所有控件的基类,而CButtonUI则是按钮类
CControlUI *pWnd = new CButtonUI; //将整个背景都这设置为一个按钮
pWnd->SetText(_T("Hello World")); // 设置文字
pWnd->SetBkColor(0xFF00FF00); // 设置背景色,根据RGB颜色值设置
m_PaintManager.Init(m_hWnd);
m_PaintManager.AttachDialog(pWnd); ////将pWnd与m_PaintManager关联起来
return lRes;
}
if( m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRes) )
{
return lRes;
}
return __super::HandleMessage(uMsg, wParam, lParam);
}
protected:
CPaintManagerUI m_PaintManager; //CPaintManagerUI类是对UI控件管理的类
};
//程序入口函数
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
CPaintManagerUI::SetInstance(hInstance);
CDuiFrameWnd duiFrame;
duiFrame.Create(NULL, _T("DUIWnd"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
//UI_WNDSTYLE_FRAME 窗口的边框风格
duiFrame.ShowModal(); //模态窗口:当这个窗口打开时,其前面的窗口就不能操作
return 0;
}
(2)接下来我们创建一个XML文件
首先在创建的工程的Debug目录下创建一个XML文件,并且在XML文件中添加以下信息:
<?xml version="1.0" encoding="UTF-8"?>
<Window size="800,600"> <!-- 窗口的初始尺寸 -->
<HorizontalLayout bkcolor="#FF00FF00"> <!-- 整个窗口的背景 按照RGB颜色值设置-->
<Button name="btn" text="Hello World"/> <!-- 按钮的属性,如名称、文本 -->
</HorizontalLayout>
</Window>
然后在上面捕获创建窗口信息的代码中,添加下面命令。
//创建XML文件
CDialogBuilder builder;
CControlUI* pRoot = builder.Create(_T("123.xml"), (UINT)0, NULL&m_PaintManager);
//_T()中为创建的XML文件名称
ASSERT(pRoot && "Failed to parse XML");
但是在Duilib中对XML文件操作也进行了封装,所以就有了更简单的方法,直接调用下面的代码
//WindowImplBase类直接封装了前面提到的全部操作,所以继承它,让代码更加的简便
class CDuiFrameWnd : public WindowImplBase
{
public:
virtual LPCTSTR GetWindowClassName() const { return _T("DUIMainFrame"); }
virtual CDuiString GetSkinFile() { return _T("123.xml"); }
virtual CDuiString GetSkinFolder() { return _T(""); }
virtual void Notify(TNotifyUI& msg) {}
};
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
CPaintManagerUI::SetInstance(hInstance);
CDuiFrameWnd duiFrame;
duiFrame.Create(NULL, _T("DUIWnd"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
duiFrame.CenterWindow();
duiFrame.ShowModal();
return 0;
}
2.修改UI界面
通过前面创建好的XMl文件,就可以在上面对界面进行各种控件的添加,具体XML文件就不放上来了,个人根据个人喜好构建页面就行了。
根据前面的项目功能概述,确定要添加的主要控件有:
- 按钮控件:Button
- 组合框控件:Combo
- 文本编辑框控件:Edit / RichEdit(RichEdit可以自主换行)
- 标签控件:Label
- 列表控件:List
按钮控件:
- BTN_CLOSE:窗口关闭按钮。
- BTN_MIN:窗口最小化按钮。
- BTN_LOAD_FILE:加载视频路径按钮,从文件夹中选取指定视频文件,并将路径加载到文本框中。
- BTN_CUT:截取视频按钮,将前面选择的视频文件,根据输入的起始时间和结束时间截取对应时间段的视频。
- BTN_GENERATE:生成GIF按钮,根据组合框中的选项,将图片或者视频生成对应的GIF图。
- BTN_LOADSRT:加载srt文件按钮,将srt文件加载到List控件中。
- BTN_GENASS:生成ass文件按钮,根据srt文件以及修改的字幕,生成新的ass文件。
- BTN_MODIFY:修改字幕按钮,选择List控件中的字幕。对其进行修改。
- BTN_SLASS:烧录ass文件按钮,将生成的新的ass字幕文件烧录到视频文件中。
组合框控件:
- COMBOX_SELECT:其中含有两个元素,其内容分别为图片和视频,可根据两个元素的选择最后BTN_GENERATE按钮响应不同的功能。
文本编辑框控件
- EDIT_PATH:用于显示BTN_LOAD_FILE按钮加载的视频文件路径。
- EDIT_START_TIME:截取视频的起始时间。
- EDIT_FINISH_TIME:截取视频的结束时间。
- RichEdit1:用于修改字幕。
列表控件:
- LIST_TIME_WORD:用于加载srt文件中的内容。
3.控件功能实现
要处理对应的按钮消息响应,需要在重载的Notify函数中捕获按钮消息,举个例子
virtual void Notify(TNotifyUI& msg)
{
//在pSender中存放有控件信息,通过GetName函数获取控件对应的名称,就可以对对应的控件进行相应的处理
CDuiString strControlName = msg.pSender->GetName();
if (msg.sType == _T("click")) //"click"表示鼠标点击消息
{
if (strControlName == _T("BTN_CLOSE")) //响应窗口关闭按钮
Close(); //库中封装好了关闭窗口功能,直接调用Close函数就行了
else if (strControlName == _T("BTN_MIN")) //响应窗口最小化按钮
SendMessage(WM_SYSCOMMAND, SC_MINIMIZE); //同样库中有封装好的对应功能
}
}
这样就处理好了两个按钮功能,接下来实现其他的控件空能。
- BTN_LOAD_FILE
当我们选择要将视频生成GIF时,首先需要从文件中选取指定的视频文件,这里我们就需要用到OPENFILENAME这样一个结构体,具体看下面代码吧
void LoadFile()
{
//首先创建结构体变量
OPENFILENAME ofn;
TCHAR FileName[MAX_PATH];
memset(&ofn, 0, sizeof(OPENFILENAME));
memset(FileName, 0, sizeof(char) * MAX_PATH);
//设置参数
ofn.lStructSize = sizeof(OPENFILENAME); //定义结构体变量的长度
ofn.lpstrFilter = _T("视频\0*.mp4;*.flv;*.rmvb;*.avi\0"); //文件过滤器,让其能够将所列出的视频格式文件显示出来
ofn.lpstrFile = FileName; //指向文件名缓冲区,如果这个缓冲区中已经包含了一个文件名,那么对话框初始化的时候将显示这个文件名,所以前面需要将FileName清空一下。当用户选择了一个文件的时候,函数在这里返回新的文件名
ofn.nMaxFile = MAX_PATH; //指定lpstrFile参数指向的缓冲区的长度
ofn.nFilterIndex = 1;
ofn.Flags = OFN_FILEMUSTEXIST; //该标志字段决定了对话框的不同行为,OFN_FILEMUSTEXIS表示用户只能选择一个已经存在的文件名
//将选择的视频文件路径写入到EDIT_PATH文本框中
if(GetOpenFileName(&ofn))
{
//利用FindControl()函数,参数为控件名称就可以找到相对应的控件进行操作
//SetText()函数为设置文本操作
m_PaintManager.FindControl(_T("EDIT_PATH"))->SetText(ofn.lpstrFile);
}
}
- BTN_CUT
截取视频操作需要用到FFmpeg库中的命令,而想要通过代码想Windows下的cmd窗口发送命令,需要用到结构体SHELLEXECUTEINFO。首先我们构造好FFmpeg命令,然后再将命令传给结构体参数,最后再向cmd发送命令就可以了。由于后面的控件功能中也需要向cmd窗口发送命令,所以就先完成一个发送命令的函数吧。
//将构造好的命令作为函数形参
void SendCMD(const CDuiString& strCMD)
{
//初始化结构体
SHELLEXECUTEINFO SEInfo;
memset(&SEInfo, 0, sizeof(SHELLEXECUTEINFO));
SEInfo.cbsize = sizeof(SHELLEXECUTEINFO); //结构体变量长度
SEInfo.fMask = SEE_MASK_NOCLOSEPROCESS; //
SEInfo.lpFile = _T("C:/Windows/System32/cmd.exe"); //要操作的对象,这里是cmd窗口的路径
//构造向cmd窗口发送的命令
CDuiString strFFmpegCMD;
strFFmpegCMD += _T("/c"); //构造的命令前面一定要记得加"/c"
strFFmpegCMD = strCMD;
SEInfo.lpParameters = strFFmpegCMD;//运行/打开程序的参数,而我们使用的是cmd窗口,所以参数就是向cmd窗口发送的命令
SEInfo.nShow = SW_HIDE;//在与运行程序代码时,如果不加这个cmd窗口会闪出以下,加上这条设置可以隐藏窗口
//发送命令
ShellExecuteEx(&SEInfo); //发送并执行命令
WaitForSingleObject(SEInof.hProcess, INFINITE);//让主线程无限等待知道命令执行完之后
}
完成了发送命令函数之后,接下来就来构造命令就行了。
void CutView()
{
//截取视频的命令是:ffmpeg -ss 起始时间 -to 结束时间 -i 输入文件 -vcodec copy -acodec copy输出文件
//-ss 后接时间,单位为秒,表示跳转到视频的该时间位置
//-to 表示从起始时间到结束时间,如果为-t 表示从起始时间开始经过多少秒
//-vcodec 表示是视频文件 后接copy表示拷贝
//-acodec 表示音频文件 后接copy表示拷贝
CDuiString startTime = ((CEditUI*)m_PaintManager.FindControl(_T("EDIT_START_TIME")))->GetText();//获取到起始时间
CDuiString finishTime = ((CEditUI*)m_PaintManager.FindControl(_T("EDIT_FINNISH_TIME")))->GetText();//获取到结束时间
CDuiString strViewPath = ((CEditUI*)m_PaintManager.FindControl(_T("EDIT_PATH")))->GetText(); //获取到视频路径
//如果视频路径没有加载,则弹出提示框显示错误
if(strViewPath.IsEmpty())
{
MessageBox(m_hWnd, _T("视频路径未加载"),_T("截取"), IDOK);
return ;
}
//设置一个输出文件路径,根据自己选择
//CPaintManagerUI::GetIntancePath()是获取该程序exe文件的路径
CDuiString strOutPath = CPaintManagerUI::GetIntancePath() + _T("ffmpeg\\GifView\\out.mp4");
//构造命令
CDuiString strCMD;
strCMD += _T("ffmpeg -ss ");
strCMD += startTime + _T(" -to ") + finishTime;
strCMD += _T(" -i ") + strViewPath;
strCMD += _T(" -vcodec copy -acodec copy ") + strOutPath + _T(" -y");
//最后加的"-y"是为了如果重复生成相同文件,可以直接替换掉,不然会一直卡在cmd窗口处,窗口提示我们是否要覆盖掉原来的文件,但是我们在程序运行时不能看到cmd窗口,也就无法选择yes覆盖掉,就会一直卡在cmd执行命令处
//最后发送命令
SendCMD(strCMD);
}
- BTN_LOADSRT
对于字幕文件,在ffmpeg库中,可以通过命令”ffmpeg -i output.mp4 -an -vn -scodec copy TimeWord.ass”直接提出到,生成ass字幕文件。但是现在的视频大部分都是硬字幕,通过这个命令是没有办法直接提取的,所以就需要借助第三方软件,这里使用的是Esrxp工具。而在这里我们需要先生成srt字幕文件,是因为我们需要对字幕进行修改,然后生成新的ass文件。但是在实际擦操作过程中我发现这个第三方工具只能识别rmvb的视频文件,而对于我安装的ffmpeg,是无法直接生成rmvb的视频文件的。这样的话,需要先将我们前面截取的MP4视频文件修改成rmvb文件(直接该文件名后缀尽可以了),然后利用Esrxp工具生成srt文件。但是为了方便后面的操作,还需要对srt文件稍微修改以下,就是把多余的空行删除。下面给一个修改好后的srt文件例子吧。
接下来需要将srt文件加载到LIST_TIME_WORD控件中 ,将上面的信息逐条显示。在这个过程中,我们将srt文件内容读取到缓冲区中,这时数据是ASIC编码格式,而我们显示到LIST_TIME_WORD控件中时应该是Unicode编码格式,所以需要转码。
//ANSI转Unicode
wstring ANSIToUnicode(const string& str)
{
int len = 0;
len = str.length();
int unicodeLen = ::MultiByteToWideChar(CP_ACP,
0,
str.c_str(),
-1,
NULL,
0);
wchar_t * pUnicode;
pUnicode = new wchar_t[unicodeLen + 1];
memset(pUnicode, 0, (unicodeLen + 1)*sizeof(wchar_t));
::MultiByteToWideChar(CP_ACP,
0,
str.c_str(),
-1,
(LPWSTR)pUnicode,
unicodeLen);
wstring rt(pUnicode);
delete pUnicode;
return rt;
}
void LoadSrt()
{
//获取srt文件路径,就是前面利用第三方工具获得的srt文件保存的路径,视情况不同
CDuiString strPath = CPaintManager::GetInstancePath();
strPath += _T("ffmpeg//word.srt");
//绑定srt文件
ifstream ifn(strPath.GetData());
//找到List控件
CListUI* pListUI = (CListUI*)m_PaintManaget.FindControl(_T("LIST_TIME_WORD"));
pListUI->RemoveAll(); //如果再次点击啊加载srt按钮,让上一次加载的内容清空
//将srt文件内容加载到LIST_TIME_WORD中
char word[256];
while(!ifn.eof())
{
//每获取一行数据,就创建一个新的文本元素行,添加到LIST_TIME_WORD中
CListTextElementUI* pListItem = new CListTextElementUI;
pListUI->Add(pListItem);
//加载时间文本
//由于getline得到的数据为ASIC编码格式,所以需要转码成Unicode编码格式
ifn.getline(word, 256);
wstring strTime = ANSItoUnicode(word);
if(strTime.empty())
continue;
strTime[strTime.find(',')] = '.'; //上面看到srt文件中时间项里面是有一个','的,而后面生成ass文件时,该位置因改为'.'所以在这里将其修改
strTime[strTime.find(',')] = '.';
pListItem->SetText(0, strTime.c_str()); //加载到LIST_TIME_WORD上,因为在使用xml设置List控件时,该控件有两列,第一列是时间,第二列是文字,所以在这里第一个参数是0
//文本
ifn.getline(word, 256);
wstring strWord = ANSIToUnicode(word);
pListItem->SetText(1, strWord.c_str());//文字在第二列,所以第一个参数传1
}
ifn.close();
}
- BTN_MODIFY
修改字幕是在srt文件加载好的基础上,首先通过鼠标点击选中要修改的那一行字幕(只修改文本,不修改时间),然后就会在RichEdit1文本编辑框中出现所选中的文本,直接对其修改就行了。所以在完成修改功能之前,需要完成鼠标点击选中LIST_TIME_WORD中内容的功能,具体实现如下:
//因为是鼠标点击选择,所以因该类似与点击按钮,但是不同的是msg.sType应该为_T("itemselect")
if (msg.sType == _T("itemselect"))
{
if (strControlName == _T("LIST_TIME_WORD"))
{
//首先找到List_TIME_WORD控件以及RichEdit1控件
//然后获取到当前选择的行号
//接着获取该行号对应的内容信息
//将文本信息中的字幕文本添加到RichEdit1文本编辑框中
//为了后面Modify()使用,将M_pListUI、m_pRichEditUI、m_Select定义成类成员变量
CListUI* m_pListUI = (CListUI*)m_PaintManager.FindControl(_T("List_TIME_WORD"));
CRichEditUI* m_pRichEditUI = (CRichEditUI*)m_PaintManager.FindControl(_T("RichEdit1"));
int m_Select = m_pListUI->GetCurSel();
CListTextElementUI* pItemText = (CListTextElementUI*)m_pListUI->GetItemAt(m_Select);
CDuiString strword = pItemText->GetText(1); //文本在第二列,所以传一个1
m_pRichEditUI->SetText(strword);
}
}
void Modify()
{
//获取到鼠标选中的文本项
//将该选中项中的文本设置成RichEdit1中的文本
CListTextElementUI* pItemText = (CListTextElementUI*)(m_pList->GetItemAt(m_Select));
pItemText->SetText(1, m_pRichEditUI->GetText());
}
- BTN_GENASS
要生成ass文件,如果不用代码生成而使用Esrxp工具也可以生成,首先将字母提取出来,然后保存为ssa文件类型,然后再将ssa文件改为ass文件即可,但是对于ass文件我们同样也需要使用UTF-8的编码格式。得到ass文件之后,我们就可以知道ass文件中的内容,ass文件主要有以下部分(并不是每个ass文件都含有下面全部):
- [Script Info]:这一部分包含了文件内容的标题和总体信息
- [v4+ Styles]:字母正文使用的风格都在这一部分做出相关定义(ssa文件是[v4 Styles])
- [Events]:这部分包括所有的事件,基本上屏幕上出现的所有内容都集中在这一部分
- [Fonts]:如果想把字体内嵌入字幕文件中,那么字体文件必须采用数字编码后放在这一部分后
- [Graphics]:如果选择内嵌图片,那么这一部分就是包含了所有用到的数字编码格式的图片文件
接下来就根据ass文件中的内容,用代码生成ass文件。
void GenerateAss()
{
//首先创建一个ass文件
//后面在烧录ass文件时发现在ffmpeg命令中,ass文件前不能够加路径,不然烧录出来的视频是没有用的,经过尝试后发现,应该ass文件生成在工程目录下
//并且ass文件应该为UTF-8格式,所以在用fopen创建文件时,后面的打开方式也需要修改
FILE* pf = fopen(".\\..\\MakeGiffPicture\\TimeWord.ass", "w+, ccs=UTF-8");
//构造第一部分[Script info]
CDuiString strScpritInfo;
strScpritInfo += _T("[Script info]\n");
strScpritInfo += _T("Title:\n");
strScpritInfo += _T("Original Script: \n");
strScpritInfo += _T("Original Translation:\n");
strScpritInfo += _T("Original Editing: \n");
strScpritInfo += _T("Original Timing: \n");
strScpritInfo += _T("Synch Point: \n");
strScpritInfo += _T("Script Updated By: \n");
strScpritInfo += _T("Update Details: \n");
strScpritInfo += _T("ScriptType: V4.00\n");
strScpritInfo += _T("Collisions: Normal\n");
strScpritInfo += _T("PlayResY: 0\n");
strScpritInfo += _T("PlayResX: 0\n");
strScpritInfo += _T("PlayDepth: 0\n");
strScpritInfo += _T("Timer: 100.0000\n");
//将第一部分写入到文件中
fwrite(strScpritInfo.GetData(), sizeof(WCHAR), strScpritInfo.GetLength(), pf);
//构造第二部分[v4+ Styles]
CDuiString strV4style;
strV4style += _T("[v4+ Styles]\n");
strV4style += _T("Format: Name,Fontname,Fontsize,PrimaryColour,SecondaryColour,TertiaryColour,BackColour,Bold,Italic,BorderStyle,Outline,Shadow,Alignment,MarginL,MarginR,MarginV,AlphaLevel,Encoding\n");
strV4style += _T("\n");
//将第二部分写入到文件中
fwrite(strV4style.GetData(), sizeof(WCHAR), strV4style.GetLength(), pf);
//构造第三部分[Events]
CDuiString strEvent;
strEvent += _T("[Events]\n");
strEvent += _T("Format: Marked,Start,End,Style,Name,MarginL,MarginR,MarginV,Effect,Text\n");
fwrite(strEvent.GetData(), sizeof(WCHAR), strEvent.GetLength(), pf);
//前面部分基本都是固定不变的所以直接将固定的字符串写入就行了,后面的部分涉及到字母的时间以及文字,所以需要根据修改后的srt文件生成
size_t wordCount = m_pListUI->GetCount();//获取行数
for (size_t i = 0; i < wordCount; ++i)
{
CListTextElementUI* pItem = (CListTextElementUI*)m_pListUI->GetItemAt(i);
CDuiString strTime = pItem->GetText(0);//获取时间文本
CDuiString strStartTime(strTime.GetData(), strTime.Find(' '));//找到‘ ’的前一个字符为止
CDuiString strFinishTime(strTime.GetData()+strStartTime.GetLength()+5); //+5是因为" --> "这个符号
CDuiString strRowWord(_T("Dialogue: Marked=0,"));
strRowWord += strStartTime;
strRowWord += _T(",");
strRowWord += strFinishTime;
strRowWord += _T(",");
strRowWord += _T("Default,,NTP,0000,0000,0000,,");
CDuiString strWord = pItem->GetText(1);//获取文字文本
strRowWord += strWord;
strRowWord += _T("\n");
fwrite(strRowWord.GetData(), sizeof(WCHAR), strRowWord.GetLength(), pf);
}
fclose(pf);
}
- BTN_SLASS
要将修改好的ass文件烧录到视频中,只需要用到FFmpeg库中的命令即可
void BurnAss
{
//ffmpeg -i input.rmvb -vf ass=TimeWord.ass output.mp4
//-vf 音频滤镜
CDuiString strCMD;
CDuiString strInPath = CPaintManager::GetInstancePath() + _T("ffmpeg\\GifView\\output.rmvb");
CDuiString strOutPath = CpaintManager::GetInstancePath() + _T("ffmpeg\\GifView\\outview.mp4");
CDuiString strAssPath = _T("TimeWord.ass ");
strCMD += _T(ffmpeg -i );
strCMD += strInPath + _T(" -vf ass=") + strAssPath + strOutPath + _T("-y");
SendCMD(strCMD);
}
- BTN_GENERATE
最后的生成GIF图了。因为可以选择以图片或者是视频两种方式生成,所以终于要用到我们的组合框控件COMBOX_SELECT了,我在XML文件中,将组合框的第一个元素设置为图片,第二个元素设置为视频。在接收到生成GIF按钮信息时,先需要根据组合框的选项,执行相对应的函数。
else if(strControlName == _T("BTN_GENERATE"))
{
CComboBoxUI* pCombo = (CComboBoxUI*)m_PaintManager.FindControl(_T("COMBOX_SELECT"));
//第一个元素为图片,所以获取当前选择为0表示是图片
if(pCombo->GetCurSel() == 0)
GenerateWithPic();
else
GenerateWithView(); //视频方式
}
void GenerateWithPic()
{
//ffmpeg -r 1 -i input.jpg out.gif
//-r 表示帧速率,数字1就表示GIF动态图为1秒1帧
CDuiString strCMD;
CDuiString strInPath = CPaintManager::GetInstancePath() + _T("ffmpeg\\Picture\\%d.jpg ");
CDuiString strOutPath = CPaintManager::GetInstancePath() + _T("ffmpeg\\GifPicture\\out.gif");
strCMD += _T("ffmpeg -r 1 -i ") + strInPath + strOutPath + _T(" -y");
SendCMD(strCMD);
}
void GenerateWithView()
{
//ffmpeg -r 50 -i input.mp4 out.gif
CDuiString strCMD;
CDuiString strInPath = CPaintManager::GetInstancePath() + _T("ffmpeg\\GifView\\output.mp4 ");
CDuiString strOutPath = CPaintManager::GetInstancePath() + _T("ffmpeg\\GifView\\out.gif");
strCMD += _T("ffmpeg -r 50 -i ") + strInPath + strOutPath + _T(" -y");
SendCMD(strCMD);
}
五、项目运行结果
由于图片的GIF图不好看结果,就看一下视频的GIF上修改的字幕吧
六、项目总结
项目中容易遇到的几个问题:
- 一开始对于DuiLib库中的接口并不了解,也是根据网上一些资料、实例去一步一步了解的。
- 在构造命令时,很容易出错,必须要严格按照命令格式来进行构造。
- 在加载srt文件时的转码问题,一开始加载上去都是乱码,后面查阅资料才知道是编码格式问题。
- 在利用ssa文件生成ass文件后,其中的[v4 Styles]并不会自动变为[v4+ Styles],所以如不不查找资料的话,可能就没有注意到这个问题。还有后面对于字幕部分的构造,也有几点需要注意的,一个是时间的里面的',' 和 '.'要进行修改,以及中间的 ” --> “这个符号。