1 /////////////////////////////////////////////////////////////////////////////
2 // Name: ProcessSlideshow.h
3 // Author: Alex Thuering
4 // Created: 26.09.2014 (refactored)
5 // RCS-ID: $Id: ProcessSlideshow.cpp,v 1.6 2016/12/11 10:06:31 ntalex Exp $
6 // Copyright: (c) Alex Thuering
7 // Licence: GPL
8 /////////////////////////////////////////////////////////////////////////////
9
10 #include "ProcessSlideshow.h"
11 #include "DVD.h"
12 #include "Config.h"
13 #include "mediaenc_ffmpeg.h"
14 #include "mediatrc_ffmpeg.h"
15 #include <wxVillaLib/utils.h>
16 #include <wxSVG/SVGDocument.h>
17 #include <wxSVG/SVGImageElement.h>
18 #include <wxSVG/SVGAnimateElement.h>
19 #include <wxSVG/mediadec_ffmpeg.h>
20
21 #define TRANSITION_FILE(fname) wxFindDataFile(wxT("transitions") + wxString(wxFILE_SEP_PATH) + fname)
22 #define TRANSITIONS_DIR wxFindDataDirectory(wxT("transitions"))
23
ProcessSlideshow(ProgressDlg * progressDlg,DVD * dvd,wxString dvdTmpDir)24 ProcessSlideshow::ProcessSlideshow(ProgressDlg* progressDlg, DVD* dvd, wxString dvdTmpDir): ProcessTranscode(progressDlg) {
25 this->dvd = dvd;
26 slideshowSubSteps = 0;
27 for (int tsi = 0; tsi < (int) dvd->GetTitlesets().Count(); tsi++) {
28 Titleset* ts = dvd->GetTitlesets()[tsi];
29 for (int pgci = 0; pgci < (int) ts->GetTitles().Count(); pgci++) {
30 Pgc* pgc = ts->GetTitles()[pgci];
31 for (int vobi = 0; vobi < (int) pgc->GetVobs().Count(); vobi++) {
32 Vob* vob = pgc->GetVobs()[vobi];
33 if (vob->GetSlideshow()) {
34 slideshowSubSteps += 10 * vob->GetSlideshow()->size();
35 if (vob->GetAudioFilenames().size() > 0)
36 slideshowSubSteps += 200; // transcode
37 vob->SetTmpFilename(dvdTmpDir
38 + wxString::Format(_T("title%d-%d-%d.vob"), tsi, pgci, vobi));
39 slideshowVobs.Add(vob);
40 }
41 }
42 }
43 }
44 }
45
46 /** Returns true, if process need be executed */
IsNeedExecute()47 bool ProcessSlideshow::IsNeedExecute() {
48 return slideshowVobs.Count() > 0;
49 }
50
51 /** Executes process */
Execute()52 bool ProcessSlideshow::Execute() {
53 if (slideshowVobs.Count() == 0)
54 return true;
55 if (progressDlg->WasCanceled())
56 return false;
57 progressDlg->SetSubSteps(slideshowSubSteps);
58 for (unsigned int i = 0; i < slideshowVobs.Count(); i++) {
59 wxString msg = wxString::Format(_("Generating slideshow %u of %lu"), i + 1, slideshowVobs.Count());
60 if (i == 0) {
61 progressDlg->AddSummaryMsg(msg);
62 } else {
63 progressDlg->ReplaceSummaryMsg(msg);
64 progressDlg->AddDetailMsg(msg);
65 }
66
67 Vob* vob = (Vob*) slideshowVobs[i];
68 AudioFormat audioFormat = dvd->GetAudioFormat();
69 if (vob->GetStreams().size() > 0 && vob->GetStreams()[0]->GetType() == stAUDIO) {
70 Stream* stream = vob->GetStreams()[0];
71 audioFormat = stream->GetAudioFormat();
72 if (audioFormat == afCOPY) {
73 // set destination format if it set to COPY and codec is not mp2/ac3 or sample rate != 48 kHz
74 if (stream->GetSourceCodecName() != wxT("mp2")
75 && stream->GetSourceCodecName() != wxT("ac3")
76 && stream->GetSourceCodecName() != wxT("liba52")) {
77 audioFormat = dvd->GetAudioFormat();
78 } else if (stream->GetSourceSampleRate() != 48000) {
79 audioFormat = stream->GetSourceCodecName() == wxT("mp2") ? afMP2 : afAC3;
80 }
81 }
82 }
83 if (!GenerateSlideshow(vob->GetSlideshow(), vob->GetTmpFilename(), audioFormat,
84 vob->GetAudioFilenames().size() > 0 ? vob->GetAudioFilenames()[0] : wxString(), dvd->GetAudioBitrate()))
85 return false;
86 }
87 progressDlg->IncStep();
88 return true;
89 }
90
GenerateSlideshow(Slideshow * slideshow,const wxString & vobFile,AudioFormat audioFormat,wxString audioFile,int audioBitrate)91 bool ProcessSlideshow::GenerateSlideshow(Slideshow* slideshow, const wxString& vobFile, AudioFormat audioFormat,
92 wxString audioFile, int audioBitrate) {
93 if (progressDlg->WasCanceled())
94 return false;
95
96 wxString m2vFile = vobFile + wxT("_video.m2v");
97
98 wxFfmpegMediaEncoder ffmpeg(s_config.GetThreadCount());
99 if (!ffmpeg.BeginEncode(audioFile.length() ? m2vFile : vobFile, slideshow->GetVideoFormat(),
100 audioFile.length() ? afNONE : audioFormat, slideshow->GetAspectRatio(),
101 s_config.GetSlideshowVideoBitrate(), s_config.GetSlideshowVideoCBR())) {
102 progressDlg->Failed(_("Error creation of slideshow"));
103 return false;
104 }
105
106 int width = GetFrameSize(slideshow->GetVideoFormat()).GetWidth();
107 int height = GetFrameSize(slideshow->GetVideoFormat()).GetHeight();
108
109 wxArrayString transitions;
110 wxString fname = wxFindFirstFile(TRANSITIONS_DIR + _T("*.xml"));
111 while (!fname.IsEmpty()) {
112 transitions.Add(wxFileName(fname).GetFullName());
113 fname = wxFindNextFile();
114 }
115
116 int tLast = -1;
117 srand(time(NULL));
118 for (unsigned i = 0; i < slideshow->size(); i++) {
119 if (progressDlg->WasCanceled())
120 return false;
121 wxString msg = wxString::Format(_("Converting slide %u image to video"), i + 1);
122 if (i == 0) {
123 progressDlg->AddDetailMsg(msg);
124 } else {
125 progressDlg->ReplaceLastDetailMsg(msg);
126 }
127 wxYield();
128 wxImage img = slideshow->GetImage(i);
129 if (!ffmpeg.EncodeImage(img, (int)(slideshow->GetDuration()*slideshow->GetFPS()), progressDlg)) {
130 progressDlg->Failed(_("Error creation of slideshow"));
131 return false;
132 }
133 // Transition
134 wxString transition = slideshow->GetSlide(i)->GetTransition();
135 if (transition.length() == 0)
136 transition = slideshow->GetTransition();
137 if (transition.length() && transition != wxT("none") && i != slideshow->size() - 1) {
138 if (transition == wxT("random")) {
139 int t;
140 do {
141 t = rand() % transitions.size();
142 } while (t == tLast);
143 tLast = t;
144 transition = transitions[t];
145 }
146 wxSVGDocument svg;
147 if (!LoadTransition(svg, transition, slideshow, slideshow->GetSlide(i), slideshow->GetSlide(i + 1)))
148 return false;
149 int frameCount = slideshow->GetFPS();
150 for (int f = 0; f < frameCount; f++) {
151 wxImage img = svg.Render(width, height, NULL, false);
152 if (!ffmpeg.EncodeImage(img, 1, progressDlg)) {
153 progressDlg->Failed(_("Error creation of slideshow"));
154 return false;
155 }
156 svg.SetCurrentTime(((double) f) / slideshow->GetFPS());
157 }
158 }
159 progressDlg->IncSubStep(10);
160 }
161 ffmpeg.EndEncode();
162
163 if (audioFile.length()) {
164 // mplex (and optionally transcode audio)
165 if (progressDlg->WasCanceled())
166 return false;
167
168 wxFfmpegMediaDecoder ffmpeg;
169 if (!ffmpeg.Load(audioFile))
170 return false;
171 double audioDuration = ffmpeg.GetDuration();
172 progressDlg->AddDetailMsg(_("Audio file duration: ") + Time2String(audioDuration * 1000));
173
174 int resultDuration = slideshow->GetResultDuration();
175 wxString concatFile;
176 if (audioDuration > 0 && audioDuration < resultDuration) {
177 // concate
178 progressDlg->AddDetailMsg(_("Concat audio files"));
179 int n = resultDuration / audioDuration + 1;
180 // concatFile = vobFile + wxT("_audio.txt");
181 // wxTextFile file(concatFile);
182 // file.Create();
183 // file.AddLine(wxT("ffconcat version 1.0"));
184 // for (int i = 0; i < n; i++)
185 // file.AddLine(wxT("file ") + audioFile);
186 // file.Write();
187 //ffmpeg -i 1.mp2 -i 1.mp2 -filter_complex "[0:0] [1:0] concat=n=2:v=0:a=1 [a]" -map "[a]"
188 wxFfmpegMediaTranscoder transcoder;
189 concatFile = vobFile + wxString(wxT("_audio.")) + (audioFormat == afMP2 ? wxT("mp2") : wxT("ac3"));
190 for (int i = 0; i < n; i++) {
191 transcoder.AddInputFile(audioFile);
192 }
193 transcoder.ConcatAudio(concatFile, n);
194 AVConvExecute exec(progressDlg, 0);
195 if (!exec.Execute(transcoder.GetCmd())) {
196 if (wxFileExists(concatFile))
197 wxRemoveFile(concatFile);
198 progressDlg->Failed(_("Error transcoding of ") + audioFile);
199 return false;
200 }
201 }
202
203 progressDlg->AddDetailMsg(_("Multiplexing audio and video"));
204
205 Vob slideShowVob;
206 slideShowVob.SetFilename(m2vFile);
207 slideShowVob.AddAudioFile(concatFile.length() ? concatFile : audioFile);
208 for (unsigned int i=0; i<slideShowVob.GetStreams().size(); i++) {
209 Stream* stream = slideShowVob.GetStreams()[i];
210 if (stream->GetType() == stAUDIO)
211 stream->SetAudioFormat(audioFormat);
212 }
213 slideShowVob.SetTmpFilename(vobFile);
214 slideShowVob.SetRecordingTime(resultDuration);
215 if (!Transcode(&slideShowVob, slideshow->GetAspectRatio(), s_config.GetSlideshowVideoBitrate(),
216 audioBitrate, s_config.GetUseMplexForMenus()))
217 return false;
218 if (s_config.GetRemoveTempFiles()) {
219 DeleteFile(m2vFile);
220 if (concatFile.length())
221 DeleteFile(concatFile);
222 }
223 }
224
225 wxYield();
226 return true;
227 }
228
LoadTransition(wxSVGDocument & svg,const wxString & fileName,Slideshow * slideshow,Slide * slide1,Slide * slide2)229 bool ProcessSlideshow::LoadTransition(wxSVGDocument& svg, const wxString& fileName, Slideshow* slideshow, Slide* slide1,
230 Slide* slide2) {
231 if (!svg.Load(TRANSITION_FILE(fileName))) {
232 progressDlg->Failed(_("Error loading transition definition"));
233 return false;
234 }
235 svg.GetRootElement()->SetWidth(slideshow->GetResolution().GetWidth());
236 svg.GetRootElement()->SetHeight(slideshow->GetResolution().GetHeight());
237
238 wxSVGImageElement* img1 = (wxSVGImageElement*) svg.GetElementById(wxT("image1"));
239 wxSVGImageElement* img2 = (wxSVGImageElement*) svg.GetElementById(wxT("image2"));
240 if (!img1 || !img2) {
241 progressDlg->Failed(wxT("Transition: image element with id 'image1' and/or 'image2' is not defined"));
242 return false;
243 }
244
245 img1->SetHref(slide1->GetFilename());
246 img2->SetHref(slide2->GetFilename());
247
248 SetAnimationDur(svg.GetRootElement(), s_config.GetDefTransitionDuration());
249 return true;
250 }
251
SetAnimationDur(wxSVGElement * parent,double dur)252 void ProcessSlideshow::SetAnimationDur(wxSVGElement* parent, double dur) {
253 wxSVGElement* elem = (wxSVGElement*) parent->GetChildren();
254 while (elem) {
255 if (elem->GetType() == wxSVGXML_ELEMENT_NODE) {
256 if (elem->GetDtd() == wxSVG_ANIMATE_ELEMENT) {
257 wxSVGAnimateElement* animateElem = (wxSVGAnimateElement*) elem;
258 animateElem->SetDur(dur);
259 } else if (elem->GetChildren()) {
260 SetAnimationDur(elem, dur);
261 }
262 }
263 elem = (wxSVGElement*) elem->GetNext();
264 }
265 }
266