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