update harvid & ffmpeg binary path discovery on windows (64 & 32 bit)
[ardour.git] / gtk2_ardour / transcode_ffmpeg.cc
1 /*
2     Copyright (C) 2010-2013 Paul Davis
3     Author: Robin Gareus <robin@gareus.org>
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20 #include <stdio.h>
21 #include <string.h>
22 #include <sstream>
23 #include <sys/types.h>
24
25 #include "pbd/error.h"
26 #include "pbd/convert.h"
27 #include "pbd/file_utils.h"
28 #include "gui_thread.h"
29
30 #include "transcode_ffmpeg.h"
31 #include "utils_videotl.h"
32
33 #ifdef PLATFORM_WINDOWS
34 #include <windows.h>
35 #include <shlobj.h> // CSIDL_*
36 #include "pbd/windows_special_dirs.h"
37 #endif
38
39 #include "i18n.h"
40
41 using namespace PBD;
42 using namespace VideoUtils;
43
44 TranscodeFfmpeg::TranscodeFfmpeg (std::string f)
45         : infile(f)
46 {
47         probeok = false;
48         ffexecok = false;
49         ffmpeg_exe = "";
50         ffprobe_exe = "";
51         m_duration = 0;
52         m_avoffset = m_lead_in = m_lead_out = 0;
53         m_width = m_height = 0;
54         m_aspect = m_fps = 0;
55         m_sar = "";
56 #if 1 /* tentative debug mode */
57         debug_enable = false;
58 #endif
59
60 #ifdef PLATFORM_WINDOWS
61         HKEY key;
62         DWORD size = PATH_MAX;
63         char tmp[PATH_MAX+1];
64         const char *program_files = PBD::get_win_special_folder (CSIDL_PROGRAM_FILES);
65 #endif
66
67         std::string ff_file_path;
68         if (find_file (Searchpath(Glib::getenv("PATH")), X_("ffmpeg_harvid"), ff_file_path)) {
69                 ffmpeg_exe = ff_file_path;
70         }
71 #ifdef PLATFORM_WINDOWS
72         else if ( (ERROR_SUCCESS == RegOpenKeyExA (HKEY_LOCAL_MACHINE, "Software\\RSS\\harvid", 0, KEY_READ, &key))
73                         &&  (ERROR_SUCCESS == RegQueryValueExA (key, "Install_Dir", 0, NULL, reinterpret_cast<LPBYTE>(tmp), &size))
74                         )
75         {
76                 ffmpeg_exe = g_build_filename(Glib::locale_to_utf8(tmp).c_str(), X_("ffmpeg.exe"), NULL);
77                 ffprobe_exe = g_build_filename(Glib::locale_to_utf8(tmp).c_str(), X_("ffprobe.exe"), NULL);
78         }
79         else if ( (ERROR_SUCCESS == RegOpenKeyExA (HKEY_LOCAL_MACHINE, "Software\\RSS\\harvid", 0, KEY_READ | KEY_WOW64_32KEY, &key))
80                         &&  (ERROR_SUCCESS == RegQueryValueExA (key, "Install_Dir", 0, NULL, reinterpret_cast<LPBYTE>(tmp), &size))
81                         )
82         {
83                 ffmpeg_exe = g_build_filename(Glib::locale_to_utf8(tmp).c_str(), X_("ffmpeg.exe"), NULL);
84                 ffprobe_exe = g_build_filename(Glib::locale_to_utf8(tmp).c_str(), X_("ffprobe.exe"), NULL);
85         }
86         if (Glib::file_test(ffmpeg_exe, Glib::FILE_TEST_EXISTS)) {
87                 ;
88         }
89         else if (program_files && Glib::file_test(g_build_filename(program_files, "harvid", "ffmpeg.exe", NULL), Glib::FILE_TEST_EXISTS)) {
90                 ffmpeg_exe = g_build_filename(program_files, "harvid", "ffmpeg.exe", NULL);
91         }
92         else if (program_files && Glib::file_test(g_build_filename(program_files, "ffmpeg", "ffmpeg.exe", NULL), Glib::FILE_TEST_EXISTS)) {
93                 ffmpeg_exe = g_build_filename(program_files, "harvid", "ffmpeg.exe", NULL);
94         }
95         /* generic fallbacks to try */
96         else if (Glib::file_test(X_("C:\\Program Files\\harvid\\ffmpeg.exe"), Glib::FILE_TEST_EXISTS)) {
97                 ffmpeg_exe = X_("C:\\Program Files\\harvid\\ffmpeg.exe");
98         }
99         else if (Glib::file_test(X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe"), Glib::FILE_TEST_EXISTS)) {
100                 ffmpeg_exe = X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe");
101         } else {
102                 ffmpeg_exe = X_("");
103         }
104 #endif
105
106         if (find_file (Searchpath(Glib::getenv("PATH")), X_("ffprobe_harvid"), ff_file_path)) {
107                 ffprobe_exe = ff_file_path;
108         }
109 #ifdef PLATFORM_WINDOWS
110         if (Glib::file_test(ffprobe_exe, Glib::FILE_TEST_EXISTS)) {
111                 ;
112         }
113         else if (program_files && Glib::file_test(g_build_filename(program_files, "harvid", "ffprobe.exe", NULL), Glib::FILE_TEST_EXISTS)) {
114                 ffmpeg_exe = g_build_filename(program_files, "harvid", "ffprobe.exe", NULL);
115         }
116         else if (program_files && Glib::file_test(g_build_filename(program_files, "ffmpeg", "ffprobe.exe", NULL), Glib::FILE_TEST_EXISTS)) {
117                 ffmpeg_exe = g_build_filename(program_files, "harvid", "ffprobe.exe", NULL);
118         }
119         /* generic fallbacks to try */
120         else if (Glib::file_test(X_("C:\\Program Files\\harvid\\ffprobe.exe"), Glib::FILE_TEST_EXISTS)) {
121                 ffprobe_exe = X_("C:\\Program Files\\harvid\\ffprobe.exe");
122         }
123         else if (Glib::file_test(X_("C:\\Program Files\\ffmpeg\\ffprobe.exe"), Glib::FILE_TEST_EXISTS)) {
124                 ffprobe_exe = X_("C:\\Program Files\\ffmpeg\\ffprobe.exe");
125         } else {
126                 ffprobe_exe = X_("");
127         }
128 #endif
129
130         if (ffmpeg_exe.empty() || ffprobe_exe.empty()) {
131                 warning << string_compose(
132                                 _(
133                                         "No ffprobe or ffmpeg executables could be found on this system.\n"
134                                         "Video import and export is not possible until you install those tools.\n"
135                                         "%1 requires ffmpeg and ffprobe from ffmpeg.org - version 1.1 or newer.\n"
136                                         "\n"
137                                         "The tools are included with the %1 releases from ardour.org "
138                                         "and also available with the video-server at http://x42.github.com/harvid/\n"
139                                         "\n"
140                                         "Important: the files need to be installed in $PATH and named ffmpeg_harvid and ffprobe_harvid.\n"
141                                         "If you already have a suitable ffmpeg installation on your system, we recommend creating "
142                                         "symbolic links from ffmpeg to ffmpeg_harvid and from ffprobe to ffprobe_harvid.\n"
143                                         "\n"
144                                         "see also http://manual.ardour.org/video-timeline/setup/"
145                                  ), PROGRAM_NAME) << endmsg;
146                 return;
147         }
148         ffexecok = true;
149
150         if (infile.empty() || !probe()) {
151                 return;
152         }
153         probeok = true;
154 }
155
156 TranscodeFfmpeg::~TranscodeFfmpeg ()
157 {
158   ;
159 }
160
161 bool
162 TranscodeFfmpeg::probe ()
163 {
164         ffoutput = "";
165         char **argp;
166         argp=(char**) calloc(7,sizeof(char*));
167         argp[0] = strdup(ffprobe_exe.c_str());
168         argp[1] = strdup("-print_format");
169         argp[2] = strdup("csv=nk=0");
170         argp[3] = strdup("-show_format");
171         argp[4] = strdup("-show_streams");
172         argp[5] = strdup(infile.c_str());
173         argp[6] = 0;
174         ffcmd = new ARDOUR::SystemExec(ffprobe_exe, argp);
175         ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffprobeparse, this, _1 ,_2));
176         ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
177         if (ffcmd->start(1)) {
178                 ffexit();
179                 return false;
180         }
181
182         /* wait for ffprobe process to exit */
183         ffcmd->wait();
184
185         /* wait for interposer thread to copy all data.
186          * SystemExec::Terminated is emitted and ffcmd set to NULL */
187         int timeout = 300; // 1.5 sec
188         while (ffcmd && --timeout > 0) {
189                 Glib::usleep(5000);
190         }
191         if (timeout == 0 || ffoutput.empty()) {
192                 return false;
193         }
194
195         /* parse */
196
197         std::vector<std::vector<std::string> > lines;
198         ParseCSV(ffoutput, lines);
199         double timebase = 0;
200         m_width = m_height = 0;
201         m_fps = m_aspect = 0;
202         m_duration = 0;
203         m_sar.clear();
204         m_codec.clear();
205         m_audio.clear();
206
207 #define PARSE_FRACTIONAL_FPS(VAR) \
208         { \
209                 std::string::size_type pos; \
210                 VAR = atof(value); \
211                 pos = value.find_first_of('/'); \
212                 if (pos != std::string::npos) { \
213                         VAR = atof(value.substr(0, pos)) / atof(value.substr(pos+1)); \
214                 } \
215         }
216
217         for (std::vector<std::vector<std::string> >::iterator i = lines.begin(); i != lines.end(); ++i) {
218                 if (i->at(0) == X_("format")) {
219                         /* format,filename,#streams,format-name,format-long-name,start-time,duration,size,bitrate */
220                 } else
221                 if (i->at(0) == X_("stream")) {
222                         if (i->at(5) == X_("codec_type=video") && m_width == 0) {
223
224                                 for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
225                                         const size_t kvsep = kv->find('=');
226                                         if(kvsep == std::string::npos) continue;
227                                         std::string key = kv->substr(0, kvsep);
228                                         std::string value = kv->substr(kvsep + 1);
229
230                                         if (key == X_("index")) {
231                                                 m_videoidx = atoi(value);
232                                         } else if (key == X_("width")) {
233                                                 m_width = atoi(value);
234                                         } else if (key == X_("height")) {
235                                                 m_height = atoi(value);
236                                         } else if (key == X_("codec_name")) {
237                                                 if (!m_codec.empty()) m_codec += " ";
238                                                 m_codec += value;
239                                         } else if (key == X_("codec_long_name")) {
240                                                 if (!m_codec.empty()) m_codec += " ";
241                                                 m_codec += "[" + value + "]";
242                                         } else if (key == X_("codec_tag_string")) {
243                                                 if (!m_codec.empty()) m_codec += " ";
244                                                 m_codec += "(" + value + ")";
245                                         } else if (key == X_("r_frame_rate")) {
246                                                 PARSE_FRACTIONAL_FPS(m_fps)
247                                         } else if (key == X_("avg_frame_rate") && m_fps == 0) {
248                                                 PARSE_FRACTIONAL_FPS(m_fps)
249                                         } else if (key == X_("time_base")) {
250                                                 PARSE_FRACTIONAL_FPS(timebase)
251                                         } else if (key == X_("timecode") && m_duration == 0) {
252                                                 int h,m,s; char f[7];
253                                                 if (sscanf(i->at(16).c_str(), "%d:%d:%d:%s",&h,&m,&s,f) == 4) {
254                                                         m_duration = (ARDOUR::framecnt_t) floor(m_fps * (
255                                                                         h * 3600.0
256                                                                 + m * 60.0
257                                                                 + s * 1.0
258                                                                 + atoi(f) / pow((double)10, (int)strlen(f))
259                                                         ));
260                                                 }
261                                         } else if (key == X_("duration_ts") && m_fps == 0 && timebase !=0 ) {
262                                                 m_duration = atof(value) * m_fps * timebase;
263                                         } else if (key == X_("duration") && m_fps != 0 && m_duration == 0) {
264                                                 m_duration = atof(value) * m_fps;
265                                         } else if (key == X_("sample_aspect_ratio")) {
266                                                 std::string::size_type pos;
267                                                 pos = value.find_first_of(':');
268                                                 if (pos != std::string::npos && atof(value.substr(pos+1)) != 0) {
269                                                         m_sar = value;
270                                                         m_sar.replace(pos, 1, "/");
271                                                 }
272                                         } else if (key == X_("display_aspect_ratio")) {
273                                                 std::string::size_type pos;
274                                                 pos = value.find_first_of(':');
275                                                 if (pos != std::string::npos && atof(value.substr(pos+1)) != 0) {
276                                                         m_aspect = atof(value.substr(0, pos)) / atof(value.substr(pos+1));
277                                                 }
278                                         }
279                                 }
280
281                                 if (m_aspect == 0) {
282                                         m_aspect = (double)m_width / (double)m_height;
283                                 }
284
285                         } else if (i->at(5) == X_("codec_type=audio")) { /* new ffprobe */
286                                 FFAudioStream as;
287                                 for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
288                                         const size_t kvsep = kv->find('=');
289                                         if(kvsep == std::string::npos) continue;
290                                         std::string key = kv->substr(0, kvsep);
291                                         std::string value = kv->substr(kvsep + 1);
292
293                                         if (key == X_("channels")) {
294                                                 as.channels   = atoi(value);
295                                         } else if (key == X_("index")) {
296                                                 as.stream_id  = value;
297                                         } else if (key == X_("codec_long_name")) {
298                                                 if (!as.name.empty()) as.name += " ";
299                                                 as.name += value;
300                                         } else if (key == X_("codec_name")) {
301                                                 if (!as.name.empty()) as.name += " ";
302                                                 as.name += value;
303                                         } else if (key == X_("sample_fmt")) {
304                                                 if (!as.name.empty()) as.name += " ";
305                                                 as.name += "FMT:" + value;
306                                         } else if (key == X_("sample_rate")) {
307                                                 if (!as.name.empty()) as.name += " ";
308                                                 as.name += "SR:" + value;
309                                         }
310
311                                 }
312                                 m_audio.push_back(as);
313                         }
314                 }
315         }
316         /* end parse */
317
318 #if 0 /* DEBUG */
319         printf("FPS: %f\n", m_fps);
320         printf("Duration: %lu frames\n",(unsigned long)m_duration);
321         printf("W/H: %ix%i\n",m_width, m_height);
322         printf("aspect: %f\n",m_aspect);
323         printf("codec: %s\n",m_codec.c_str());
324         if (m_audio.size() > 0) {
325                 for (AudioStreams::iterator it = m_audio.begin(); it < m_audio.end(); ++it) {
326                         printf("audio: %s - %i channels\n",(*it).stream_id.c_str(), (*it).channels);
327                 }
328         } else {
329           printf("audio: no audio streams in file.\n");
330         }
331 #endif
332
333         return true;
334 }
335
336 TranscodeFfmpeg::FFSettings
337 TranscodeFfmpeg::default_encoder_settings ()
338 {
339         TranscodeFfmpeg::FFSettings ffs;
340         ffs.clear();
341         ffs["-vcodec"] = "mpeg4";
342         ffs["-acodec"] = "ac3";
343         ffs["-b:v"] = "5000k";
344         ffs["-b:a"] = "160k";
345         return ffs;
346 }
347
348 TranscodeFfmpeg::FFSettings
349 TranscodeFfmpeg::default_meta_data ()
350 {
351         TranscodeFfmpeg::FFSettings ffm;
352         ffm.clear();
353         ffm["comment"] = "Created with ardour";
354         return ffm;
355 }
356
357 char *
358 TranscodeFfmpeg::format_metadata (std::string key, std::string value)
359 {
360         size_t start_pos = 0;
361         std::string v1 = value;
362         while((start_pos = v1.find_first_not_of(
363                         "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789(),.\"'",
364                         start_pos)) != std::string::npos)
365         {
366                 v1.replace(start_pos, 1, "_");
367                 start_pos += 1;
368         }
369
370         start_pos = 0;
371         while((start_pos = v1.find("\"", start_pos)) != std::string::npos) {
372                 v1.replace(start_pos, 1, "\\\"");
373                 start_pos += 2;
374         }
375
376         size_t len = key.length() + v1.length() + 4;
377         char *mds = (char*) calloc(len, sizeof(char));
378         snprintf(mds, len, "%s=\"%s\"", key.c_str(), v1.c_str());
379         return mds;
380 }
381
382 bool
383 TranscodeFfmpeg::encode (std::string outfile, std::string inf_a, std::string inf_v, TranscodeFfmpeg::FFSettings ffs, TranscodeFfmpeg::FFSettings meta, bool map)
384 {
385 #define MAX_FFMPEG_ENCODER_ARGS (100)
386         char **argp;
387         int a=0;
388
389         argp=(char**) calloc(MAX_FFMPEG_ENCODER_ARGS,sizeof(char*));
390         argp[a++] = strdup(ffmpeg_exe.c_str());
391         if (m_avoffset < 0 || m_avoffset > 0) {
392                 std::ostringstream osstream; osstream << m_avoffset;
393                 argp[a++] = strdup("-itsoffset");
394                 argp[a++] = strdup(osstream.str().c_str());
395         }
396         argp[a++] = strdup("-i");
397         argp[a++] = strdup(inf_v.c_str());
398
399         argp[a++] = strdup("-i");
400         argp[a++] = strdup(inf_a.c_str());
401
402         for(TranscodeFfmpeg::FFSettings::const_iterator it = ffs.begin(); it != ffs.end(); ++it) {
403                 argp[a++] = strdup(it->first.c_str());
404                 argp[a++] = strdup(it->second.c_str());
405         }
406         for(TranscodeFfmpeg::FFSettings::const_iterator it = meta.begin(); it != meta.end(); ++it) {
407                 argp[a++] = strdup("-metadata");
408                 argp[a++] = format_metadata(it->first.c_str(), it->second.c_str());
409         }
410
411         if (m_fps > 0) {
412                 m_lead_in  = rint (m_lead_in * m_fps) / m_fps;
413                 m_lead_out = rint (m_lead_out * m_fps) / m_fps;
414         }
415
416         if (m_lead_in != 0 && m_lead_out != 0) {
417                 std::ostringstream osstream;
418                 argp[a++] = strdup("-vf");
419                 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_in;
420                 if (!m_sar.empty()) osstream << X_(":sar=") << m_sar;
421                 osstream << X_(" [pre]; ");
422                 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_out;
423                 if (!m_sar.empty()) osstream << X_(":sar=") << m_sar;
424                 osstream << X_(" [post]; ");
425                 osstream << X_("[pre] [in] [post] concat=n=3");
426                 argp[a++] = strdup(osstream.str().c_str());
427         } else if (m_lead_in != 0) {
428                 std::ostringstream osstream;
429                 argp[a++] = strdup("-vf");
430                 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_in;
431                 if (!m_sar.empty()) osstream << X_(":sar=") << m_sar;
432                 osstream << X_(" [pre]; ");
433                 osstream << X_("[pre] [in] concat=n=2");
434                 argp[a++] = strdup(osstream.str().c_str());
435         } else if (m_lead_out != 0) {
436                 std::ostringstream osstream;
437                 argp[a++] = strdup("-vf");
438                 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_out;
439                 if (!m_sar.empty()) osstream << X_(":sar=") << m_sar;
440                 osstream << X_(" [post]; ");
441                 osstream << X_("[in] [post] concat=n=2");
442                 argp[a++] = strdup(osstream.str().c_str());
443         }
444
445         if (map) {
446                 std::ostringstream osstream;
447                 argp[a++] = strdup("-map");
448                 osstream << X_("0:") << m_videoidx;
449                 argp[a++] = strdup(osstream.str().c_str());
450                 argp[a++] = strdup("-map");
451                 argp[a++] = strdup("1:0");
452         }
453
454         argp[a++] = strdup("-y");
455         argp[a++] = strdup(outfile.c_str());
456         argp[a] = (char *)0;
457         assert(a<MAX_FFMPEG_ENCODER_ARGS);
458         /* Note: these are free()d in ~SystemExec */
459 #if 1 /* DEBUG */
460         if (debug_enable) { /* tentative debug mode */
461         printf("EXPORT ENCODE:\n");
462         for (int i=0; i< a; ++i) {
463           printf("%s ", argp[i]);
464         }
465         printf("\n");
466         }
467 #endif
468
469         ffcmd = new ARDOUR::SystemExec(ffmpeg_exe, argp);
470         ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
471         ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
472         if (ffcmd->start(2)) {
473                 ffexit();
474                 return false;
475         }
476         return true;
477 }
478
479 bool
480 TranscodeFfmpeg::extract_audio (std::string outfile, ARDOUR::framecnt_t /*samplerate*/, unsigned int stream)
481 {
482         if (!probeok) return false;
483   if (stream >= m_audio.size()) return false;
484
485         char **argp;
486         int i = 0;
487
488         argp=(char**) calloc(15,sizeof(char*));
489         argp[i++] = strdup(ffmpeg_exe.c_str());
490         argp[i++] = strdup("-i");
491         argp[i++] = strdup(infile.c_str());
492 #if 0 /* ffmpeg write original samplerate, use a3/SRC to resample */
493         argp[i++] = strdup("-ar");
494         argp[i] = (char*) calloc(7,sizeof(char)); snprintf(argp[i++], 7, "%"PRId64, samplerate);
495 #endif
496         argp[i++] = strdup("-ac");
497         argp[i] = (char*) calloc(3,sizeof(char)); snprintf(argp[i++], 3, "%i", m_audio.at(stream).channels);
498         argp[i++] = strdup("-map");
499         argp[i] = (char*) calloc(8,sizeof(char)); snprintf(argp[i++], 8, "0:%s", m_audio.at(stream).stream_id.c_str());
500         argp[i++] = strdup("-vn");
501         argp[i++] = strdup("-acodec");
502         argp[i++] = strdup("pcm_f32le");
503         argp[i++] = strdup("-y");
504         argp[i++] = strdup(outfile.c_str());
505         argp[i++] = (char *)0;
506         /* Note: argp is free()d in ~SystemExec */
507 #if 1 /* DEBUG */
508         if (debug_enable) { /* tentative debug mode */
509         printf("EXTRACT AUDIO:\n");
510         for (int i=0; i< 14; ++i) {
511           printf("%s ", argp[i]);
512         }
513         printf("\n");
514         }
515 #endif
516
517         ffcmd = new ARDOUR::SystemExec(ffmpeg_exe, argp);
518         ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_a, this, _1 ,_2));
519         ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
520         if (ffcmd->start(2)) {
521                 ffexit();
522                 return false;
523         }
524         return true;
525 }
526
527
528 bool
529 TranscodeFfmpeg::transcode (std::string outfile, const int outw, const int outh, const int kbitps)
530 {
531         if (!probeok) return false;
532
533         char **argp;
534         int bitrate = kbitps;
535         int width = outw;
536         int height = outh;
537
538         if (width < 1 || width > m_width) { width = m_width; } /* don't allow upscaling */
539         if (height < 1 || height > m_height) { height = floor(width / m_aspect); }
540
541         if (bitrate == 0) {
542                 const double bitperpixel = .7; /* avg quality */
543                 bitrate = floor(m_fps * width * height * bitperpixel / 10000.0);
544         } else {
545                 bitrate = bitrate / 10;
546         }
547         if (bitrate < 10)  bitrate = 10;
548         if (bitrate > 1000) bitrate = 1000;
549
550         argp=(char**) calloc(16,sizeof(char*));
551         argp[0] = strdup(ffmpeg_exe.c_str());
552         argp[1] = strdup("-i");
553         argp[2] = strdup(infile.c_str());
554         argp[3] = strdup("-b:v");
555         argp[4] = (char*) calloc(7,sizeof(char)); snprintf(argp[4], 7, "%i0k", bitrate);
556         argp[5] = strdup("-s");
557         argp[6] = (char*) calloc(10,sizeof(char)); snprintf(argp[6], 10, "%ix%i", width, height);
558         argp[7] = strdup("-y");
559         argp[8] = strdup("-vcodec");
560         argp[9] = strdup("mjpeg");
561         argp[10] = strdup("-an");
562         argp[11] = strdup("-intra");
563         argp[12] = strdup("-g");
564         argp[13] = strdup("1");
565         argp[14] = strdup(outfile.c_str());
566         argp[15] = (char *)0;
567         /* Note: these are free()d in ~SystemExec */
568 #if 1 /* DEBUG */
569         if (debug_enable) { /* tentative debug mode */
570         printf("TRANSCODE VIDEO:\n");
571         for (int i=0; i< 15; ++i) {
572           printf("%s ", argp[i]);
573         }
574         printf("\n");
575         }
576 #endif
577         ffcmd = new ARDOUR::SystemExec(ffmpeg_exe, argp);
578         ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
579         ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
580         if (ffcmd->start(2)) {
581                 ffexit();
582                 return false;
583         }
584         return true;
585 }
586
587 void
588 TranscodeFfmpeg::cancel ()
589 {
590         if (!ffcmd || !ffcmd->is_running()) { return;}
591         ffcmd->write_to_stdin("q");
592 #ifdef PLATFORM_WINDOWS
593         Sleep(1000);
594 #else
595         sleep (1);
596 #endif
597         if (ffcmd) {
598           ffcmd->terminate();
599         }
600 }
601
602 void
603 TranscodeFfmpeg::ffexit ()
604 {
605         delete ffcmd;
606         ffcmd=0;
607         Finished(); /* EMIT SIGNAL */
608 }
609
610 void
611 TranscodeFfmpeg::ffprobeparse (std::string d, size_t /* s */)
612 {
613         ffoutput+=d;
614 }
615
616 void
617 TranscodeFfmpeg::ffmpegparse_a (std::string d, size_t /* s */)
618 {
619         const char *t;
620         int h,m,s; char f[7];
621         ARDOUR::framecnt_t p = -1;
622
623         if (!(t=strstr(d.c_str(), "time="))) { return; }
624
625         if (sscanf(t+5, "%d:%d:%d.%s",&h,&m,&s,f) == 4) {
626                 p = (ARDOUR::framecnt_t) floor( 100.0 * (
627                       h * 3600.0
628                     + m * 60.0
629                     + s * 1.0
630                     + atoi(f) / pow((double)10, (int)strlen(f))
631                 ));
632                 p = p * m_fps / 100.0;
633                 if (p > m_duration ) { p = m_duration; }
634                 Progress(p, m_duration); /* EMIT SIGNAL */
635         } else {
636                 Progress(0, 0); /* EMIT SIGNAL */
637         }
638 }
639
640 void
641 TranscodeFfmpeg::ffmpegparse_v (std::string d, size_t /* s */)
642 {
643         if (strstr(d.c_str(), "ERROR") || strstr(d.c_str(), "Error") || strstr(d.c_str(), "error")) {
644                 warning << "ffmpeg-error: " << d << endmsg;
645         }
646         if (strncmp(d.c_str(), "frame=",6)) {
647 #if 1 /* DEBUG */
648                 if (debug_enable) {
649                         d.erase(d.find_last_not_of(" \t\r\n") + 1);
650                   printf("ffmpeg: '%s'\n", d.c_str());
651                 }
652 #endif
653                 Progress(0, 0); /* EMIT SIGNAL */
654                 return;
655         }
656         ARDOUR::framecnt_t f = atol(d.substr(6));
657         if (f == 0) {
658                 Progress(0, 0); /* EMIT SIGNAL */
659         } else {
660                 Progress(f, m_duration); /* EMIT SIGNAL */
661         }
662 }