1 /////////////////////////////////////////////////////////////////////////////
2 // Name: ProcessMenu.cpp
3 // Author: Alex Thuering
4 // Created: 26.09.2014 (refactored)
5 // RCS-ID: $Id: ProcessMenu.cpp,v 1.11 2016/12/11 10:06:31 ntalex Exp $
6 // Copyright: (c) Alex Thuering
7 // Licence: GPL
8 /////////////////////////////////////////////////////////////////////////////
9
10 #include "ProcessMenu.h"
11 #include "DVD.h"
12 #include "Menu.h"
13 #include "Config.h"
14 #include "mediaenc_ffmpeg.h"
15 #include "mediatrc_ffmpeg.h"
16 #include <wxSVG/SVGDocument.h>
17 #include <wxSVG/SVGImageElement.h>
18 #include <wxSVG/SVGAnimateElement.h>
19 #include <wxSVG/SVGAnimateTransformElement.h>
20 #include <wxSVG/mediadec_ffmpeg.h>
21
22 #define WARNING_MPLEX_MSG wxT("Warning: using of mplex (mjpegs-tool) for generation of menus with audio is disabled. This can produce not compliant DVD.")
23
24 /** Constructor */
ProcessMenu(ProgressDlg * progressDlg,DVD * dvd,wxString dvdTmpDir)25 ProcessMenu::ProcessMenu(ProgressDlg* progressDlg, DVD* dvd, wxString dvdTmpDir): ProcessTranscode(progressDlg) {
26 this->dvd = dvd;
27 menuSubSteps = 0;
28 for (int tsi = -1; tsi < (int) dvd->GetTitlesets().Count(); tsi++) {
29 PgcArray& pgcs = dvd->GetPgcArray(tsi, true);
30 for (int pgci = 0; pgci < (int) pgcs.Count(); pgci++) {
31 Pgc* pgc = pgcs[pgci];
32 if (pgc->GetVobs().Count() == 0)
33 continue;
34 // set temp file name
35 wxString menuFile = dvdTmpDir + wxString::Format(_T("menu%d-%d.mpg"), tsi + 1, pgci);
36 Vob* vob = pgc->GetVobs()[0];
37 vob->SetTmpFilename(menuFile);
38 // calculate sub steps
39 Menu* menu = vob->GetMenu();
40 WidescreenType widescreenType = pgcs.GetVideo().GetWidescreen();
41 bool videoMenu = menu->GetSVG()->GetDuration() > 0;
42 if (videoMenu) {
43 menuSubSteps += 400; // generate mpeg(200) + transcode(200)
44 } else if (vob->GetAudioFilenames().size() > 0 || s_config.GetUseMplexForMenus())
45 menuSubSteps += 300; // generate mpeg(25+75) + transcode(200)
46 else
47 menuSubSteps += 25; // generate mpeg
48 menuSubSteps += (menu->GetAspectRatio() == ar4_3 ? 1 : widescreenType != wtAUTO ? 2 : 3) * 50; // spumux
49 menuVobs.Add(vob);
50 menuWSTypes.Add((int) widescreenType);
51 }
52 }
53 }
54
55 /** Returns true, if process need be executed */
IsNeedExecute()56 bool ProcessMenu::IsNeedExecute() {
57 return menuVobs.Count() > 0;
58 }
59
60 /** Executes process */
Execute()61 bool ProcessMenu::Execute() {
62 if (menuVobs.Count() == 0)
63 return true;
64 if (progressDlg->WasCanceled())
65 return false;
66 progressDlg->SetSubSteps(menuSubSteps);
67 for (unsigned int i = 0; i < menuVobs.Count(); i++) {
68 wxString msg = wxString::Format(_("Generating menu %u of %lu"), i + 1, menuVobs.Count());
69 if (i == 0) {
70 progressDlg->AddSummaryMsg(msg);
71 } else {
72 progressDlg->ReplaceSummaryMsg(msg);
73 progressDlg->AddDetailMsg(msg);
74 }
75 Vob* vob = (Vob*) menuVobs[i];
76 WidescreenType wsType = (WidescreenType) menuWSTypes[i];
77
78 wxString audioFile;
79 AudioFormat audioFormat = dvd->GetAudioFormat();
80 if (vob->GetAudioFilenames().size() > 0 && vob->GetStreams().size() > 0
81 && vob->GetStreams()[0]->GetType() == stAUDIO) {
82 audioFile = vob->GetAudioFilenames()[0];
83 Stream* stream = vob->GetStreams()[0];
84 audioFormat = stream->GetAudioFormat();
85 if (audioFormat == afCOPY) {
86 // set destination format if it set to COPY and codec is not mp2/ac3 or sample rate != 48 kHz
87 if (stream->GetSourceCodecName() != wxT("mp2")
88 && stream->GetSourceCodecName() != wxT("ac3")
89 && stream->GetSourceCodecName() != wxT("liba52")) {
90 audioFormat = dvd->GetAudioFormat();
91 } else if (stream->GetSourceSampleRate() != 48000) {
92 audioFormat = stream->GetSourceCodecName() == wxT("mp2") ? afMP2 : afAC3;
93 }
94 }
95 }
96
97 if (!GenerateMenu(vob->GetMenu(), wsType, vob->GetTmpFilename(), audioFormat, audioFile, dvd->GetAudioBitrate()))
98 return false;
99 }
100 progressDlg->IncStep();
101 return true;
102 }
103
GenerateMenu(Menu * menu,WidescreenType widescreenType,const wxString & menuFile,AudioFormat audioFormat,wxString audioFile,int audioBitrate)104 bool ProcessMenu::GenerateMenu(Menu* menu, WidescreenType widescreenType, const wxString& menuFile,
105 AudioFormat audioFormat, wxString audioFile, int audioBitrate) {
106 if (progressDlg->WasCanceled())
107 return false;
108 wxString mpegFile = menuFile + _T("_bg.mpg");
109 wxString m2vFile = menuFile + _T("_bg.m2v");
110 wxString audioFileTmp = menuFile + _T("_bg.audio");
111 wxString btFile = menuFile + _T("_buttons.png");
112 wxString hlFile = menuFile + _T("_highlight.png");
113 wxString selFile = menuFile + _T("_select.png");
114 wxString spuFile = menuFile + _T("_spumux.xml");
115
116 bool videoMenu = menu->GetSVG()->GetDuration() > 0;
117 wxYield();
118
119 progressDlg->AddDetailMsg(_("Create menu MPEG"));
120 if (s_config.GetMenuVideoBitrate() < 1000)
121 s_config.SetMenuVideoBitrate(1000);
122
123 if (videoMenu) {
124 // get background audio format
125 AudioFormat bgAudioFormat = afNONE;
126 wxString bgAudioOutputFormat;
127 if (menu->HasVideoBackground() && audioFile.length() == 0) {
128 wxFfmpegMediaDecoder ffmpeg;
129 if (!ffmpeg.Load(menu->GetBackground()))
130 return false;
131 for (unsigned int i = 0; i < ffmpeg.GetStreamCount(); i++) {
132 if (ffmpeg.GetStreamType(i) == stAUDIO) {
133 if (ffmpeg.GetCodecName(i) != wxT("mp2") && ffmpeg.GetCodecName(i) != wxT("ac3")) {
134 bgAudioFormat = audioFormat;
135 } else if (ffmpeg.GetSampleRate(i) != 48000) {
136 bgAudioFormat = ffmpeg.GetCodecName(i) == wxT("mp2") ? afMP2 : afAC3;
137 } else {
138 bgAudioFormat = afCOPY;
139 bgAudioOutputFormat = ffmpeg.GetCodecName(i);
140 }
141 break;
142 }
143 }
144 }
145 // encode video
146 bool hasAudio = audioFile.length() || bgAudioFormat != afNONE || s_config.GetUseMplexForMenus();
147 wxFfmpegMediaEncoder ffmpeg(s_config.GetThreadCount());
148 if (!ffmpeg.BeginEncode(hasAudio ? m2vFile : mpegFile, menu->GetVideoFormat(), hasAudio ? afNONE : audioFormat,
149 menu->GetAspectRatio(), s_config.GetMenuVideoBitrate(), s_config.GetMenuVideoCBR())) {
150 progressDlg->Failed(_("Error creation of menu"));
151 return false;
152 }
153 wxSVGDocument* svg = menu->GetBackgroundSVG();
154 int width = menu->GetFrameResolution().GetWidth();
155 int height = menu->GetFrameResolution().GetHeight();
156 progressDlg->AddDetailMsg(wxString::Format(_("Video duration: %f sec"), svg->GetDuration()));
157 int frameCount = (int) (svg->GetDuration() * GetFps(menu->GetVideoFormat(), false));
158 if (frameCount == 0)
159 frameCount = s_config.GetMenuFrameCount();
160 for (int f = 0; f < frameCount; f++) {
161 if (progressDlg->WasCanceled())
162 return false;
163 if (f % 10 == 0) {
164 wxString msg = wxString::Format(_("Encoding frame %d of %d"), (f > 0 ? f : 1), frameCount);
165 if (f == 0) {
166 progressDlg->AddDetailMsg(msg);
167 } else {
168 progressDlg->ReplaceLastDetailMsg(msg);
169 }
170 }
171 svg->SetCurrentTime(((double) f) / GetFps(menu->GetVideoFormat(), false));
172 wxImage img = svg->Render(width, height, NULL, false);
173 if (!ffmpeg.EncodeImage(img, 1, progressDlg)) {
174 progressDlg->Failed(_("Error creation of menu"));
175 return false;
176 }
177 progressDlg->SetSubStep(200 * (f + 1) / frameCount);
178 }
179 ffmpeg.EndEncode();
180 if (hasAudio) {
181 if (audioFile.length() == 0 && bgAudioFormat == afNONE) {
182 // encode audio
183 audioFileTmp = menuFile + wxString(wxT("_bg.")) + (audioFormat == afAC3 ? wxT("ac3") : wxT("mp2"));
184 double duration = (double) frameCount / GetFps(menu->GetVideoFormat(), false);
185 if (!ffmpeg.BeginEncode(audioFileTmp, vfNONE, audioFormat, menu->GetAspectRatio(),
186 s_config.GetMenuVideoBitrate(), s_config.GetMenuVideoCBR())
187 || !ffmpeg.EncodeAudio(duration, progressDlg)) {
188 progressDlg->Failed(_("Error creation of menu"));
189 return false;
190 }
191 ffmpeg.EndEncode();
192 }
193 Vob menuVob;
194 menuVob.SetFilename(m2vFile);
195 menuVob.GetVideoStream()->SetDestinationFormat(vfCOPY);
196 if (audioFile.length()) {
197 menuVob.AddAudioFile(audioFile);
198 } else if (bgAudioFormat == afNONE) {
199 menuVob.AddAudioFile(audioFileTmp);
200 } else {
201 progressDlg->AddDetailMsg(_("Transcode audio from") + wxString(wxT(" ")) + menu->GetBackground());
202 // transcode audio
203 wxFfmpegMediaTranscoder transcoder;
204 if (!transcoder.AddInputFile(menu->GetBackground())) {
205 progressDlg->Failed(wxT("Error by transcoding of ") + menu->GetBackground());
206 return false;
207 }
208 transcoder.SetOutputFormat(bgAudioOutputFormat);
209 if (!transcoder.SetOutputFile(audioFileTmp, vfNONE, false, menu->GetAspectRatio(), bgAudioFormat,
210 sfNONE, 0, false, audioBitrate)) {
211 progressDlg->Failed(_("Error transcoding of ") + menu->GetBackground());
212 return false;
213 }
214 AVConvExecute exec(progressDlg, -1);
215 if (!exec.Execute(transcoder.GetCmd())) {
216 DeleteFile(audioFileTmp);
217 progressDlg->Failed(_("Error transcoding of ") + menu->GetBackground());
218 return false;
219 }
220 menuVob.AddAudioFile(audioFileTmp);
221 }
222 progressDlg->AddDetailMsg(_("Multiplexing audio and video"));
223 for (unsigned int i = 0; i < menuVob.GetStreams().size(); i++) {
224 Stream* stream = menuVob.GetStreams()[i];
225 if (stream->GetType() == stAUDIO && stream->GetAudioFormat() == afCOPY) {
226 // change destination format if it set to COPY and codec is not mp2/ac3 or sample rate != 48 kHz
227 if (stream->GetSourceCodecName() != wxT("mp2") && stream->GetSourceCodecName() != wxT("ac3")) {
228 stream->SetDestinationFormat(audioFormat);
229 } else if (stream->GetSourceSampleRate() != 48000) {
230 stream->SetDestinationFormat(stream->GetSourceCodecName() == wxT("mp2") ? afMP2 : afAC3);
231 }
232 }
233 }
234 menuVob.SetKeepAspectRatio(false);
235 menuVob.SetTmpFilename(mpegFile);
236 if (!s_config.GetUseMplexForMenus())
237 progressDlg->AddDetailMsg(WARNING_MPLEX_MSG, *wxRED);
238 if (!Transcode(&menuVob, menu->GetAspectRatio(), s_config.GetMenuVideoBitrate(), audioBitrate,
239 s_config.GetUseMplexForMenus()))
240 return false;
241 if (s_config.GetRemoveTempFiles()) {
242 DeleteFile(m2vFile);
243 DeleteFile(audioFileTmp);
244 }
245 }
246 } else { // menu with still image
247 wxImage bgImage = menu->GetBackgroundImage();
248 if (!bgImage.Ok()) {
249 progressDlg->Failed(_("Error creation of menu"));
250 return false;
251 }
252
253 bool hasAudio = audioFile.length(); // || s_config.GetUseMplexForMenus(); disable mplex for still image
254 int frameCount = s_config.GetMenuFrameCount();
255 if (audioFile.length() > 0) {
256 // get audio duration
257 wxFfmpegMediaDecoder decoder;
258 if (decoder.Load(audioFile)) {
259 double duration = decoder.GetDuration();
260 if (duration > 0) {
261 progressDlg->AddDetailMsg(wxString::Format(_("Audio duration: %f sec"), duration));
262 if (menu->GetVideoFormat() == vfPAL)
263 frameCount = (int) (duration * 25);
264 else
265 frameCount = (int) (duration * 30000 / 1001);
266 }
267 }
268 }
269
270 // encode video
271 wxFfmpegMediaEncoder ffmpeg(s_config.GetThreadCount());
272 if (frameCount < (isNTSC(menu->GetVideoFormat()) ? 15 : 12)) {
273 frameCount = (isNTSC(menu->GetVideoFormat()) ? 15 : 12); // set minimum of frames to default GOP size
274 }
275 progressDlg->AddDetailMsg(_("Frame count of menu:") + wxString::Format(wxT(" %d"), frameCount));
276 if (!ffmpeg.BeginEncode(hasAudio ? m2vFile : mpegFile, menu->GetVideoFormat(), hasAudio ? afNONE : audioFormat,
277 menu->GetAspectRatio(), s_config.GetMenuVideoBitrate(), s_config.GetMenuVideoCBR())
278 || !ffmpeg.EncodeImage(bgImage, frameCount, progressDlg)) {
279 progressDlg->Failed(_("Error creation of menu"));
280 return false;
281 }
282 ffmpeg.EndEncode();
283 progressDlg->IncSubStep(25);
284
285 if (hasAudio) { // audio file or mplex is used
286 if (audioFile.length() == 0) {
287 // encode audio
288 audioFileTmp = menuFile + wxString(wxT("_bg.")) + (audioFormat == afAC3 ? wxT("ac3") : wxT("mp2"));
289 double duration = (double) frameCount / GetFps(menu->GetVideoFormat(), false);
290 if (!ffmpeg.BeginEncode(audioFileTmp, vfNONE, audioFormat, menu->GetAspectRatio(),
291 s_config.GetMenuVideoBitrate(), s_config.GetMenuVideoCBR())
292 || !ffmpeg.EncodeAudio(duration, progressDlg)) {
293 progressDlg->Failed(_("Error creation of menu"));
294 return false;
295 }
296 ffmpeg.EndEncode();
297 }
298 progressDlg->IncSubStep(75);
299 // mplex (and optionally transcode audio)
300 if (progressDlg->WasCanceled())
301 return false;
302 progressDlg->AddDetailMsg(_("Multiplexing audio and video"));
303 Vob menuVob;
304 menuVob.SetFilename(m2vFile);
305 menuVob.AddAudioFile(audioFile.length() ? audioFile : audioFileTmp);
306 for (unsigned int i = 0; i < menuVob.GetStreams().size(); i++) {
307 Stream* stream = menuVob.GetStreams()[i];
308 if (stream->GetType() == stAUDIO)
309 stream->SetDestinationFormat(audioFile.length() ? audioFormat : afCOPY);
310 }
311 menuVob.SetTmpFilename(mpegFile);
312 if (!s_config.GetUseMplexForMenus())
313 progressDlg->AddDetailMsg(WARNING_MPLEX_MSG, *wxRED);
314 if (!Transcode(&menuVob, menu->GetAspectRatio(), s_config.GetMenuVideoBitrate(), audioBitrate,
315 s_config.GetUseMplexForMenus()))
316 return false;
317 if (s_config.GetRemoveTempFiles()) {
318 DeleteFile(m2vFile);
319 DeleteFile(audioFileTmp);
320 }
321 }
322 }
323
324 //spumux
325 if (progressDlg->WasCanceled())
326 return false;
327 progressDlg->AddDetailMsg(_("Multiplexing subpictures into mpeg"));
328 int stCount = menu->GetAspectRatio() == ar4_3 ? 1 : widescreenType != wtAUTO ? 2 : 3;
329 for (int stIdx = 0; stIdx < stCount; stIdx++) {
330 if (stIdx > 0) {
331 if (mpegFile == menu->GetBackground())
332 mpegFile = menuFile + _T("_bg.mpg");
333 if (!wxRenameFile(menuFile, mpegFile, false)) {
334 progressDlg->Failed(wxString::Format(_("Can't rename file '%s' in '%s'"), menuFile.c_str(), mpegFile.c_str()));
335 return false;
336 }
337 }
338 // save subpictures
339 SubStreamMode mode = menu->GetAspectRatio() == ar4_3 ? ssmNORMAL : ssmWIDESCREEN;
340 if (stIdx == 1)
341 mode = widescreenType == wtNOLETTERBOX ? ssmPANSCAN : ssmLETTERBOX;
342 else if (stIdx == 2)
343 mode = ssmPANSCAN;
344 wxImage* images = menu->GetSubPictures(mode);
345 images[0].SaveFile(btFile);
346 images[1].SaveFile(hlFile);
347 images[2].SaveFile(selFile);
348 delete[] images;
349 // save spumux
350 menu->SaveSpumux(spuFile, mode, btFile, hlFile, selFile);
351 wxString cmd = s_config.GetSpumuxCmd();
352 cmd.Replace(wxT("$FILE_CONF"), spuFile);
353 cmd.Replace(wxT("$STREAM"), wxString::Format(wxT("%d"), stIdx));
354 wxULongLong fileSize = wxFileName::GetSize(mpegFile);
355 SpumuxExecute exec(progressDlg, fileSize != wxInvalidSize ? fileSize.ToDouble() : -1);
356 if (!exec.Execute(cmd, mpegFile, menuFile)) {
357 progressDlg->Failed();
358 return false;
359 }
360 if (s_config.GetRemoveTempFiles() || stIdx + 1 < stCount) {
361 if ((!videoMenu || mpegFile != menu->GetBackground()))
362 DeleteFile(mpegFile);
363 DeleteFile(btFile);
364 DeleteFile(hlFile);
365 DeleteFile(selFile);
366 DeleteFile(spuFile);
367 }
368 }
369
370 wxYield();
371 return true;
372 }
373