master merge; new files not added after initial cairocanvas patch application
[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 #ifdef WITH_VIDEOTIMELINE
21
22 #include <stdio.h>
23 #include <string.h>
24 #include <sstream>
25 #include <sys/types.h>
26
27 #include "pbd/error.h"
28 #include "pbd/file_utils.h"
29 #include "pbd/file_utils.h"
30 #include "gui_thread.h"
31
32 #include "transcode_ffmpeg.h"
33 #include "utils_videotl.h"
34
35 #include "i18n.h"
36
37 TranscodeFfmpeg::TranscodeFfmpeg (std::string f)
38         : infile(f)
39 {
40         probeok = false;
41         ffexecok = false;
42         ffmpeg_exe = "";
43         ffprobe_exe = "";
44         m_duration = 0;
45         m_avoffset = m_lead_in = m_lead_out = 0;
46         m_width = m_height = 0;
47         m_aspect = m_fps = 0;
48 #if 1 /* tentative debug mode */
49         debug_enable = false;
50 #endif
51
52         std::string ff_file_path;
53         if (find_file_in_search_path (PBD::SearchPath(Glib::getenv("PATH")), X_("ffmpeg_harvid"), ff_file_path)) { ffmpeg_exe = ff_file_path; }
54         else if (Glib::file_test(X_("C:\\Program Files\\harvid\\ffmpeg.exe"), Glib::FILE_TEST_EXISTS)) {
55                 ffmpeg_exe = X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe");
56         }
57         else if (Glib::file_test(X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe"), Glib::FILE_TEST_EXISTS)) {
58                 ffmpeg_exe = X_("C:\\Program Files\\ffmpeg\\ffmpeg.exe");
59         }
60
61         if (find_file_in_search_path (PBD::SearchPath(Glib::getenv("PATH")), X_("ffprobe_harvid"), ff_file_path)) { ffprobe_exe = ff_file_path; }
62         else if (Glib::file_test(X_("C:\\Program Files\\harvid\\ffprobe.exe"), Glib::FILE_TEST_EXISTS)) {
63                 ffprobe_exe = X_("C:\\Program Files\\ffmpeg\\ffprobe.exe");
64         }
65         else if (Glib::file_test(X_("C:\\Program Files\\ffmpeg\\ffprobe.exe"), Glib::FILE_TEST_EXISTS)) {
66                 ffprobe_exe = X_("C:\\Program Files\\ffmpeg\\ffprobe.exe");
67         }
68
69         if (ffmpeg_exe.empty() || ffprobe_exe.empty()) {
70                 PBD::warning << _(
71                                 "No ffprobe or ffmpeg executables could be found on this system.\n"
72                                 "Video import and export is not possible until you install those tools.\n"
73                                 "Ardour requires ffmpeg and ffprobe from ffmpeg.org - version 1.1 or newer.\n"
74                                 "\n"
75                                 "The tools are included with the Ardour releases from ardour.org "
76                                 "and also available with the video-server at http://x42.github.com/harvid/\n"
77                                 "\n"
78                                 "Important: the files need to be installed in $PATH and named ffmpeg_harvid and ffprobe_harvid.\n"
79                                 "If you already have a suitable ffmpeg installation on your system, we recommend creating "
80                                 "symbolic links from ffmpeg to ffmpeg_harvid and from ffprobe to ffprobe_harvid.\n"
81                                 ) << endmsg;
82                 return;
83         }
84         ffexecok = true;
85
86         if (infile.empty() || !probe()) {
87                 return;
88         }
89         probeok = true;
90 }
91
92 TranscodeFfmpeg::~TranscodeFfmpeg ()
93 {
94   ;
95 }
96
97 bool
98 TranscodeFfmpeg::probe ()
99 {
100         ffoutput = "";
101         char **argp;
102         argp=(char**) calloc(7,sizeof(char*));
103         argp[0] = strdup(ffprobe_exe.c_str());
104         argp[1] = strdup("-print_format");
105         argp[2] = strdup("csv=nk=0");
106         argp[3] = strdup("-show_format");
107         argp[4] = strdup("-show_streams");
108         argp[5] = strdup(infile.c_str());
109         argp[6] = 0;
110         ffcmd = new SystemExec(ffprobe_exe, argp);
111         ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffprobeparse, this, _1 ,_2));
112         ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
113         if (ffcmd->start(1)) {
114                 ffexit();
115                 return false;
116         }
117         ffcmd->wait();
118
119         /* parse */
120
121         std::vector<std::vector<std::string> > lines;
122         ParseCSV(ffoutput, lines);
123         double timebase = 0;
124         m_width = m_height = 0;
125         m_fps = m_aspect = 0;
126         m_duration = 0;
127         m_codec.clear();
128         m_audio.clear();
129
130 #define PARSE_FRACTIONAL_FPS(VAR) \
131         { \
132                 std::string::size_type pos; \
133                 VAR = atof(value.c_str()); \
134                 pos = value.find_first_of('/'); \
135                 if (pos != std::string::npos) { \
136                         VAR = atof(value.substr(0, pos).c_str()) / atof(value.substr(pos+1).c_str()); \
137                 } \
138         }
139
140         for (std::vector<std::vector<std::string> >::iterator i = lines.begin(); i != lines.end(); ++i) {
141                 if (i->at(0) == X_("format")) {
142                         /* format,filename,#streams,format-name,format-long-name,start-time,duration,size,bitrate */
143                 } else
144                 if (i->at(0) == X_("stream")) {
145                         if (i->at(5) == X_("codec_type=video") && m_width == 0) {
146
147                                 for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
148                                         const size_t kvsep = kv->find('=');
149                                         if(kvsep == std::string::npos) continue;
150                                         std::string key = kv->substr(0, kvsep);
151                                         std::string value = kv->substr(kvsep + 1);
152
153                                         if (key == X_("index")) {
154                                                 m_videoidx = atoi(value.c_str());
155                                         } else if (key == X_("width")) {
156                                                 m_width = atoi(value.c_str());
157                                         } else if (key == X_("height")) {
158                                                 m_height = atoi(value.c_str());
159                                         } else if (key == X_("codec_name")) {
160                                                 if (!m_codec.empty()) m_codec += " ";
161                                                 m_codec += value;
162                                         } else if (key == X_("codec_long_name")) {
163                                                 if (!m_codec.empty()) m_codec += " ";
164                                                 m_codec += "[" + value + "]";
165                                         } else if (key == X_("codec_tag_string")) {
166                                                 if (!m_codec.empty()) m_codec += " ";
167                                                 m_codec += "(" + value + ")";
168                                         } else if (key == X_("r_frame_rate")) {
169                                                 PARSE_FRACTIONAL_FPS(m_fps)
170                                         } else if (key == X_("avg_frame_rate") && m_fps == 0) {
171                                                 PARSE_FRACTIONAL_FPS(m_fps)
172                                         } else if (key == X_("time_base")) {
173                                                 PARSE_FRACTIONAL_FPS(timebase)
174                                         } else if (key == X_("timecode") && m_duration == 0) {
175                                                 int h,m,s; char f[7];
176                                                 if (sscanf(i->at(16).c_str(), "%d:%d:%d:%s",&h,&m,&s,f) == 4) {
177                                                         m_duration = (ARDOUR::framecnt_t) floor(m_fps * (
178                                                                         h * 3600.0
179                                                                 + m * 60.0
180                                                                 + s * 1.0
181                                                                 + atoi(f) / pow(10, strlen(f))
182                                                         ));
183                                                 }
184                                         } else if (key == X_("duration_ts") && m_fps == 0 && timebase !=0 ) {
185                                                 m_duration = atof(value.c_str()) * m_fps * timebase;
186                                         } else if (key == X_("duration") && m_fps != 0 && m_duration == 0) {
187                                                 m_duration = atof(value.c_str()) * m_fps;
188                                         } else if (key == X_("display_aspect_ratio")) {
189                                                 std::string::size_type pos;
190                                                 pos = value.find_first_of(':');
191                                                 if (pos != std::string::npos && atof(value.substr(pos+1).c_str()) != 0) {
192                                                         m_aspect = atof(value.substr(0, pos).c_str()) / atof(value.substr(pos+1).c_str());
193                                                 }
194                                         }
195                                 }
196
197                                 if (m_aspect == 0) {
198                                         m_aspect = (double)m_width / (double)m_height;
199                                 }
200
201                         } else if (i->at(5) == X_("codec_type=audio")) { /* new ffprobe */
202                                 AudioStream as;
203                                 for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
204                                         const size_t kvsep = kv->find('=');
205                                         if(kvsep == std::string::npos) continue;
206                                         std::string key = kv->substr(0, kvsep);
207                                         std::string value = kv->substr(kvsep + 1);
208
209                                         if (key == X_("channels")) {
210                                                 as.channels   = atoi(value.c_str());
211                                         } else if (key == X_("index")) {
212                                                 as.stream_id  = value;
213                                         } else if (key == X_("codec_long_name")) {
214                                                 if (!as.name.empty()) as.name += " ";
215                                                 as.name += value;
216                                         } else if (key == X_("codec_name")) {
217                                                 if (!as.name.empty()) as.name += " ";
218                                                 as.name += value;
219                                         } else if (key == X_("sample_fmt")) {
220                                                 if (!as.name.empty()) as.name += " ";
221                                                 as.name += "FMT:" + value;
222                                         } else if (key == X_("sample_rate")) {
223                                                 if (!as.name.empty()) as.name += " ";
224                                                 as.name += "SR:" + value;
225                                         }
226
227                                 }
228                                 m_audio.push_back(as);
229                         }
230                 }
231         }
232         /* end parse */
233
234
235         int timeout = 500;
236         while (ffcmd && --timeout) usleep (1000); // wait until 'ffprobe' terminated.
237         if (timeout == 0) return false;
238
239 #if 0 /* DEBUG */
240         printf("FPS: %f\n", m_fps);
241         printf("Duration: %lu frames\n",(unsigned long)m_duration);
242         printf("W/H: %ix%i\n",m_width, m_height);
243         printf("aspect: %f\n",m_aspect);
244         printf("codec: %s\n",m_codec.c_str());
245         if (m_audio.size() > 0) {
246                 for (AudioStreams::iterator it = m_audio.begin(); it < m_audio.end(); ++it) {
247                         printf("audio: %s - %i channels\n",(*it).stream_id.c_str(), (*it).channels);
248                 }
249         } else {
250           printf("audio: no audio streams in file.\n");
251         }
252 #endif
253
254         return true;
255 }
256
257 FFSettings
258 TranscodeFfmpeg::default_encoder_settings ()
259 {
260         FFSettings ffs;
261         ffs.clear();
262         ffs["-vcodec"] = "mpeg4";
263         ffs["-acodec"] = "ac3";
264         ffs["-b:v"] = "5000k";
265         ffs["-b:a"] = "160k";
266         return ffs;
267 }
268
269 FFSettings
270 TranscodeFfmpeg::default_meta_data ()
271 {
272         FFSettings ffm;
273         ffm.clear();
274         ffm["comment"] = "Created with ardour";
275         return ffm;
276 }
277
278 char *
279 TranscodeFfmpeg::format_metadata (std::string key, std::string value)
280 {
281         size_t start_pos = 0;
282         std::string v1 = value;
283         while((start_pos = v1.find_first_not_of(
284                         "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789(),.\"'",
285                         start_pos)) != std::string::npos)
286         {
287                 v1.replace(start_pos, 1, "_");
288                 start_pos += 1;
289         }
290
291         start_pos = 0;
292         while((start_pos = v1.find("\"", start_pos)) != std::string::npos) {
293                 v1.replace(start_pos, 1, "\\\"");
294                 start_pos += 2;
295         }
296
297         size_t len = key.length() + v1.length() + 4;
298         char *mds = (char*) calloc(len, sizeof(char));
299         snprintf(mds, len, "%s=\"%s\"", key.c_str(), v1.c_str());
300         return mds;
301 }
302
303 bool
304 TranscodeFfmpeg::encode (std::string outfile, std::string inf_a, std::string inf_v, FFSettings ffs, FFSettings meta, bool map)
305 {
306 #define MAX_FFMPEG_ENCODER_ARGS (100)
307         char **argp;
308         int a=0;
309
310         argp=(char**) calloc(MAX_FFMPEG_ENCODER_ARGS,sizeof(char*));
311         argp[a++] = strdup(ffmpeg_exe.c_str());
312         if (m_avoffset < 0 || m_avoffset > 0) {
313                 std::ostringstream osstream; osstream << m_avoffset;
314                 argp[a++] = strdup("-itsoffset");
315                 argp[a++] = strdup(osstream.str().c_str());
316         }
317         argp[a++] = strdup("-i");
318         argp[a++] = strdup(inf_v.c_str());
319
320         argp[a++] = strdup("-i");
321         argp[a++] = strdup(inf_a.c_str());
322
323         for(FFSettings::const_iterator it = ffs.begin(); it != ffs.end(); ++it) {
324                 argp[a++] = strdup(it->first.c_str());
325                 argp[a++] = strdup(it->second.c_str());
326         }
327         for(FFSettings::const_iterator it = meta.begin(); it != meta.end(); ++it) {
328                 argp[a++] = strdup("-metadata");
329                 argp[a++] = format_metadata(it->first.c_str(), it->second.c_str());
330         }
331         if (m_lead_in != 0 && m_lead_out != 0) {
332                 std::ostringstream osstream;
333                 argp[a++] = strdup("-vf");
334                 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_in << X_(" [pre]; ");
335                 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_out << X_(" [post]; ");
336                 osstream << X_("[pre] [in] [post] concat=n=3");
337                 argp[a++] = strdup(osstream.str().c_str());
338         } else if (m_lead_in != 0) {
339                 std::ostringstream osstream;
340                 argp[a++] = strdup("-vf");
341                 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_in << X_(" [pre]; ");
342                 osstream << X_("[pre] [in] concat=n=2");
343                 argp[a++] = strdup(osstream.str().c_str());
344         } else if (m_lead_out != 0) {
345                 std::ostringstream osstream;
346                 argp[a++] = strdup("-vf");
347                 osstream << X_("color=c=black:s=") << m_width << X_("x") << m_height << X_(":d=") << m_lead_out << X_(" [post]; ");
348                 osstream << X_("[in] [post] concat=n=2");
349                 argp[a++] = strdup(osstream.str().c_str());
350         }
351
352         if (map) {
353                 std::ostringstream osstream;
354                 argp[a++] = strdup("-map");
355                 osstream << X_("0:") << m_videoidx;
356                 argp[a++] = strdup(osstream.str().c_str());
357                 argp[a++] = strdup("-map");
358                 argp[a++] = strdup("1:0");
359         }
360
361         argp[a++] = strdup("-y");
362         argp[a++] = strdup(outfile.c_str());
363         argp[a] = (char *)0;
364         assert(a<MAX_FFMPEG_ENCODER_ARGS);
365         /* Note: these are free()d in ~SystemExec */
366 #if 1 /* DEBUG */
367         if (debug_enable) { /* tentative debug mode */
368         printf("EXPORT ENCODE:\n");
369         for (int i=0; i< a; ++i) {
370           printf("%s ", argp[i]);
371         }
372         printf("\n");
373         }
374 #endif
375
376         ffcmd = new SystemExec(ffmpeg_exe, argp);
377         ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
378         ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
379         if (ffcmd->start(2)) {
380                 ffexit();
381                 return false;
382         }
383         return true;
384 }
385
386 bool
387 TranscodeFfmpeg::extract_audio (std::string outfile, ARDOUR::framecnt_t samplerate, unsigned int stream)
388 {
389         if (!probeok) return false;
390   if (stream >= m_audio.size()) return false;
391
392         char **argp;
393         int i = 0;
394
395         argp=(char**) calloc(15,sizeof(char*));
396         argp[i++] = strdup(ffmpeg_exe.c_str());
397         argp[i++] = strdup("-i");
398         argp[i++] = strdup(infile.c_str());
399 #if 0 /* ffmpeg write original samplerate, use a3/SRC to resample */
400         argp[i++] = strdup("-ar");
401         argp[i] = (char*) calloc(7,sizeof(char)); snprintf(argp[i++], 7, "%"PRId64, samplerate);
402 #endif
403         argp[i++] = strdup("-ac");
404         argp[i] = (char*) calloc(3,sizeof(char)); snprintf(argp[i++], 3, "%i", m_audio.at(stream).channels);
405         argp[i++] = strdup("-map");
406         argp[i] = (char*) calloc(8,sizeof(char)); snprintf(argp[i++], 8, "0:%s", m_audio.at(stream).stream_id.c_str());
407         argp[i++] = strdup("-vn");
408         argp[i++] = strdup("-acodec");
409         argp[i++] = strdup("pcm_f32le");
410         argp[i++] = strdup("-y");
411         argp[i++] = strdup(outfile.c_str());
412         argp[i++] = (char *)0;
413         /* Note: argp is free()d in ~SystemExec */
414 #if 1 /* DEBUG */
415         if (debug_enable) { /* tentative debug mode */
416         printf("EXTRACT AUDIO:\n");
417         for (int i=0; i< 14; ++i) {
418           printf("%s ", argp[i]);
419         }
420         printf("\n");
421         }
422 #endif
423
424         ffcmd = new SystemExec(ffmpeg_exe, argp);
425         ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_a, this, _1 ,_2));
426         ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
427         if (ffcmd->start(2)) {
428                 ffexit();
429                 return false;
430         }
431         return true;
432 }
433
434
435 bool
436 TranscodeFfmpeg::transcode (std::string outfile, const int outw, const int outh, const int kbitps)
437 {
438         if (!probeok) return false;
439
440         char **argp;
441         int bitrate = kbitps;
442         int width = outw;
443         int height = outh;
444
445         if (width < 1 || width > m_width) { width = m_width; } /* don't allow upscaling */
446         if (height < 1 || height > m_height) { height = floor(width / m_aspect); }
447
448         if (bitrate == 0) {
449                 const double bitperpixel = .7; /* avg quality */
450                 bitrate = floor(m_fps * width * height * bitperpixel / 10000.0);
451         } else {
452                 bitrate = bitrate / 10;
453         }
454         if (bitrate < 10)  bitrate = 10;
455         if (bitrate > 1000) bitrate = 1000;
456
457         argp=(char**) calloc(16,sizeof(char*));
458         argp[0] = strdup(ffmpeg_exe.c_str());
459         argp[1] = strdup("-i");
460         argp[2] = strdup(infile.c_str());
461         argp[3] = strdup("-b:v");
462         argp[4] = (char*) calloc(7,sizeof(char)); snprintf(argp[4], 7, "%i0k", bitrate);
463         argp[5] = strdup("-s");
464         argp[6] = (char*) calloc(10,sizeof(char)); snprintf(argp[6], 10, "%ix%i", width, height);
465         argp[7] = strdup("-y");
466         argp[8] = strdup("-vcodec");
467         argp[9] = strdup("mpeg4");
468         argp[10] = strdup("-an");
469         argp[11] = strdup("-intra");
470         argp[12] = strdup("-g");
471         argp[13] = strdup("1");
472         argp[14] = strdup(outfile.c_str());
473         argp[15] = (char *)0;
474         /* Note: these are free()d in ~SystemExec */
475 #if 1 /* DEBUG */
476         if (debug_enable) { /* tentative debug mode */
477         printf("TRANSCODE VIDEO:\n");
478         for (int i=0; i< 15; ++i) {
479           printf("%s ", argp[i]);
480         }
481         printf("\n");
482         }
483 #endif
484         ffcmd = new SystemExec(ffmpeg_exe, argp);
485         ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
486         ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
487         if (ffcmd->start(2)) {
488                 ffexit();
489                 return false;
490         }
491         return true;
492 }
493
494 void
495 TranscodeFfmpeg::cancel ()
496 {
497         if (!ffcmd || !ffcmd->is_running()) { return;}
498         ffcmd->write_to_stdin("q");
499         sleep (1);
500         if (ffcmd) {
501           ffcmd->terminate();
502         }
503 }
504
505 void
506 TranscodeFfmpeg::ffexit ()
507 {
508         delete ffcmd;
509         ffcmd=0;
510         Finished(); /* EMIT SIGNAL */
511 }
512
513 void
514 TranscodeFfmpeg::ffprobeparse (std::string d, size_t /* s */)
515 {
516         ffoutput+=d;
517 }
518
519 void
520 TranscodeFfmpeg::ffmpegparse_a (std::string d, size_t /* s */)
521 {
522         const char *t;
523         int h,m,s; char f[7];
524         ARDOUR::framecnt_t p = -1;
525
526         if (!(t=strstr(d.c_str(), "time="))) { return; }
527
528         if (sscanf(t+5, "%d:%d:%d.%s",&h,&m,&s,f) == 4) {
529                 p = (ARDOUR::framecnt_t) floor( 100.0 * (
530                       h * 3600.0
531                     + m * 60.0
532                     + s * 1.0
533                     + atoi(f) / pow(10, strlen(f))
534                 ));
535                 p = p * m_fps / 100.0;
536                 if (p > m_duration ) { p = m_duration; }
537                 Progress(p, m_duration); /* EMIT SIGNAL */
538         } else {
539                 Progress(0, 0); /* EMIT SIGNAL */
540         }
541 }
542
543 void
544 TranscodeFfmpeg::ffmpegparse_v (std::string d, size_t /* s */)
545 {
546         if (strstr(d.c_str(), "ERROR") || strstr(d.c_str(), "Error") || strstr(d.c_str(), "error")) {
547                 PBD::warning << "ffmpeg-error: " << d << endmsg;
548         }
549         if (strncmp(d.c_str(), "frame=",6)) {
550 #if 1 /* DEBUG */
551                 if (debug_enable) {
552                         d.erase(d.find_last_not_of(" \t\r\n") + 1);
553                   printf("ffmpeg: '%s'\n", d.c_str());
554                 }
555 #endif
556                 Progress(0, 0); /* EMIT SIGNAL */
557                 return;
558         }
559         ARDOUR::framecnt_t f = atol(d.substr(6).c_str());
560         if (f == 0) {
561                 Progress(0, 0); /* EMIT SIGNAL */
562         } else {
563                 Progress(f, m_duration); /* EMIT SIGNAL */
564         }
565 }
566
567 #endif /* WITH_VIDEOTIMELINE */