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