Merge branch 'nsm' of https://github.com/royvegard/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         double timebase = 0;
121         m_width = m_height = 0;
122         m_fps = m_aspect = 0;
123         m_duration = 0;
124         m_codec.clear();
125         m_audio.clear();
126
127 #define PARSE_FRACTIONAL_FPS(VAR) \
128         { \
129                 std::string::size_type pos; \
130                 VAR = atof(value.c_str()); \
131                 pos = value.find_first_of('/'); \
132                 if (pos != std::string::npos) { \
133                         VAR = atof(value.substr(0, pos).c_str()) / atof(value.substr(pos+1).c_str()); \
134                 } \
135         }
136
137         for (std::vector<std::vector<std::string> >::iterator i = lines.begin(); i != lines.end(); ++i) {
138                 if (i->at(0) == X_("format")) {
139                         /* format,filename,#streams,format-name,format-long-name,start-time,duration,size,bitrate */
140                 } else
141                 if (i->at(0) == X_("stream")) {
142                         if (i->at(5) == X_("codec_type=video") && m_width == 0) {
143
144                                 for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
145                                         const size_t kvsep = kv->find('=');
146                                         if(kvsep == std::string::npos) continue;
147                                         std::string key = kv->substr(0, kvsep);
148                                         std::string value = kv->substr(kvsep + 1);
149
150                                         if (key == X_("width")) {
151                                                 m_width = atoi(value.c_str());
152                                         } else if (key == X_("height")) {
153                                                 m_height = atoi(value.c_str());
154                                         } else if (key == X_("codec_name")) {
155                                                 if (!m_codec.empty()) m_codec += " ";
156                                                 m_codec += value;
157                                         } else if (key == X_("codec_long_name")) {
158                                                 if (!m_codec.empty()) m_codec += " ";
159                                                 m_codec += "[" + value + "]";
160                                         } else if (key == X_("codec_tag_string")) {
161                                                 if (!m_codec.empty()) m_codec += " ";
162                                                 m_codec += "(" + value + ")";
163                                         } else if (key == X_("r_frame_rate")) {
164                                                 PARSE_FRACTIONAL_FPS(m_fps)
165                                         } else if (key == X_("avg_frame_rate") && m_fps == 0) {
166                                                 PARSE_FRACTIONAL_FPS(m_fps)
167                                         } else if (key == X_("time_base")) {
168                                                 PARSE_FRACTIONAL_FPS(timebase)
169                                         } else if (key == X_("timecode") && m_duration == 0) {
170                                                 int h,m,s; char f[7];
171                                                 if (sscanf(i->at(16).c_str(), "%d:%d:%d:%s",&h,&m,&s,f) == 4) {
172                                                         m_duration = (ARDOUR::framecnt_t) floor(m_fps * (
173                                                                         h * 3600.0
174                                                                 + m * 60.0
175                                                                 + s * 1.0
176                                                                 + atoi(f) / pow(10, strlen(f))
177                                                         ));
178                                                 }
179                                         } else if (key == X_("duration_ts") && m_fps == 0 && timebase !=0 ) {
180                                                 m_duration = atof(value.c_str()) * m_fps * timebase;
181                                         } else if (key == X_("duration") && m_fps != 0 && m_duration == 0) {
182                                                 m_duration = atof(value.c_str()) * m_fps;
183                                         } else if (key == X_("display_aspect_ratio")) {
184                                                 std::string::size_type pos;
185                                                 pos = value.find_first_of(':');
186                                                 if (pos != std::string::npos && atof(value.substr(pos+1).c_str()) != 0) {
187                                                         m_aspect = atof(value.substr(0, pos).c_str()) / atof(value.substr(pos+1).c_str());
188                                                 }
189                                         }
190                                 }
191
192                                 if (m_aspect == 0) {
193                                         m_aspect = (double)m_width / (double)m_height;
194                                 }
195
196                         } else if (i->at(5) == X_("codec_type=audio")) { /* new ffprobe */
197                                 AudioStream as;
198                                 for (std::vector<std::string>::iterator kv = i->begin(); kv != i->end(); ++kv) {
199                                         const size_t kvsep = kv->find('=');
200                                         if(kvsep == std::string::npos) continue;
201                                         std::string key = kv->substr(0, kvsep);
202                                         std::string value = kv->substr(kvsep + 1);
203
204                                         if (key == X_("channels")) {
205                                                 as.channels   = atoi(value.c_str());
206                                         } else if (key == X_("index")) {
207                                                 as.stream_id  = value;
208                                         } else if (key == X_("codec_long_name")) {
209                                                 if (!as.name.empty()) as.name += " ";
210                                                 as.name += value;
211                                         } else if (key == X_("codec_name")) {
212                                                 if (!as.name.empty()) as.name += " ";
213                                                 as.name += value;
214                                         } else if (key == X_("sample_fmt")) {
215                                                 if (!as.name.empty()) as.name += " ";
216                                                 as.name += "FMT:" + value;
217                                         } else if (key == X_("sample_rate")) {
218                                                 if (!as.name.empty()) as.name += " ";
219                                                 as.name += "SR:" + value;
220                                         }
221
222                                 }
223                                 m_audio.push_back(as);
224                         }
225                 }
226         }
227         /* end parse */
228
229
230         int timeout = 500;
231         while (ffcmd && --timeout) usleep (1000); // wait until 'ffprobe' terminated.
232         if (timeout == 0) return false;
233
234 #if 1 /* DEBUG */
235         printf("FPS: %f\n", m_fps);
236         printf("Duration: %lu frames\n",(unsigned long)m_duration);
237         printf("W/H: %ix%i\n",m_width, m_height);
238         printf("aspect: %f\n",m_aspect);
239         printf("codec: %s\n",m_codec.c_str());
240         if (m_audio.size() > 0) {
241                 for (AudioStreams::iterator it = m_audio.begin(); it < m_audio.end(); ++it) {
242                         printf("audio: %s - %i channels\n",(*it).stream_id.c_str(), (*it).channels);
243                 }
244         } else {
245           printf("audio: no audio streams in file.\n");
246         }
247 #endif
248
249         return true;
250 }
251
252 FFSettings
253 TranscodeFfmpeg::default_encoder_settings ()
254 {
255         FFSettings ffs;
256         ffs.clear();
257         ffs["-vcodec"] = "mpeg4";
258         ffs["-acodec"] = "ac3";
259         ffs["-b:v"] = "5000k";
260         ffs["-b:a"] = "160k";
261         return ffs;
262 }
263
264 FFSettings
265 TranscodeFfmpeg::default_meta_data ()
266 {
267         FFSettings ffm;
268         ffm.clear();
269         ffm["comment"] = "Created with ardour";
270         return ffm;
271 }
272
273 char *
274 TranscodeFfmpeg::format_metadata (std::string key, std::string value)
275 {
276         size_t start_pos = 0;
277         std::string v1 = value;
278         while((start_pos = v1.find_first_not_of(
279                         "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789(),.\"'",
280                         start_pos)) != std::string::npos)
281         {
282                 v1.replace(start_pos, 1, "_");
283                 start_pos += 1;
284         }
285
286         start_pos = 0;
287         while((start_pos = v1.find("\"", start_pos)) != std::string::npos) {
288                 v1.replace(start_pos, 1, "\\\"");
289                 start_pos += 2;
290         }
291
292         size_t len = key.length() + v1.length() + 4;
293         char *mds = (char*) calloc(len, sizeof(char));
294         snprintf(mds, len, "%s=\"%s\"", key.c_str(), v1.c_str());
295         return mds;
296 }
297
298 bool
299 TranscodeFfmpeg::encode (std::string outfile, std::string inf_a, std::string inf_v, FFSettings ffs, FFSettings meta, bool map)
300 {
301 #define MAX_FFMPEG_ENCODER_ARGS (100)
302         char **argp;
303         int a=0;
304
305         argp=(char**) calloc(MAX_FFMPEG_ENCODER_ARGS,sizeof(char*));
306         argp[a++] = strdup(ffmpeg_exe.c_str());
307         if (m_avoffset < 0 || m_avoffset > 0) {
308                 std::ostringstream osstream; osstream << m_avoffset;
309                 argp[a++] = strdup("-itsoffset");
310                 argp[a++] = strdup(osstream.str().c_str());
311         }
312         argp[a++] = strdup("-i");
313         argp[a++] = strdup(inf_v.c_str());
314
315         argp[a++] = strdup("-i");
316         argp[a++] = strdup(inf_a.c_str());
317         for(FFSettings::const_iterator it = ffs.begin(); it != ffs.end(); ++it) {
318                 argp[a++] = strdup(it->first.c_str());
319                 argp[a++] = strdup(it->second.c_str());
320         }
321         for(FFSettings::const_iterator it = meta.begin(); it != meta.end(); ++it) {
322                 argp[a++] = strdup("-metadata");
323                 argp[a++] = format_metadata(it->first.c_str(), it->second.c_str());
324         }
325         if (map) {
326                 argp[a++] = strdup("-map");
327                 argp[a++] = strdup("0:0");
328                 argp[a++] = strdup("-map");
329                 argp[a++] = strdup("1:0");
330         }
331         argp[a++] = strdup("-y");
332         argp[a++] = strdup(outfile.c_str());
333         argp[a] = (char *)0;
334         assert(a<MAX_FFMPEG_ENCODER_ARGS);
335         /* Note: these are free()d in ~SystemExec */
336 #if 1 /* DEBUG */
337         if (debug_enable) { /* tentative debug mode */
338         printf("EXPORT ENCODE:\n");
339         for (int i=0; i< a; ++i) {
340           printf("%s ", argp[i]);
341         }
342         printf("\n");
343         }
344 #endif
345
346         ffcmd = new SystemExec(ffmpeg_exe, argp);
347         ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
348         ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
349         if (ffcmd->start(2)) {
350                 ffexit();
351                 return false;
352         }
353         return true;
354 }
355
356 bool
357 TranscodeFfmpeg::extract_audio (std::string outfile, ARDOUR::framecnt_t samplerate, unsigned int stream)
358 {
359         if (!probeok) return false;
360   if (stream >= m_audio.size()) return false;
361
362         char **argp;
363         int i = 0;
364
365         argp=(char**) calloc(15,sizeof(char*));
366         argp[i++] = strdup(ffmpeg_exe.c_str());
367         argp[i++] = strdup("-i");
368         argp[i++] = strdup(infile.c_str());
369 #if 0 // native samplerate -- use a3/SRC
370         argp[i++] = strdup("-ar");
371         argp[i] = (char*) calloc(7,sizeof(char)); snprintf(argp[i++], 7, "%"PRId64, samplerate);
372 #endif
373         argp[i++] = strdup("-ac");
374         argp[i] = (char*) calloc(3,sizeof(char)); snprintf(argp[i++], 3, "%i", m_audio.at(stream).channels);
375         argp[i++] = strdup("-map");
376         argp[i] = (char*) calloc(8,sizeof(char)); snprintf(argp[i++], 8, "0:%s", m_audio.at(stream).stream_id.c_str());
377         argp[i++] = strdup("-vn");
378         argp[i++] = strdup("-acodec");
379         argp[i++] = strdup("pcm_f32le");
380         argp[i++] = strdup("-y");
381         argp[i++] = strdup(outfile.c_str());
382         argp[i++] = (char *)0;
383         /* Note: argp is free()d in ~SystemExec */
384 #if 1 /* DEBUG */
385         if (debug_enable) { /* tentative debug mode */
386         printf("EXTRACT AUDIO:\n");
387         for (int i=0; i< 14; ++i) {
388           printf("%s ", argp[i]);
389         }
390         printf("\n");
391         }
392 #endif
393
394         ffcmd = new SystemExec(ffmpeg_exe, argp);
395         ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_a, this, _1 ,_2));
396         ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
397         if (ffcmd->start(2)) {
398                 ffexit();
399                 return false;
400         }
401         return true;
402 }
403
404
405 bool
406 TranscodeFfmpeg::transcode (std::string outfile, const int outw, const int outh, const int kbitps)
407 {
408         if (!probeok) return false;
409
410         char **argp;
411         int bitrate = kbitps;
412         int width = outw;
413         int height = outh;
414
415         if (width < 1 || width > m_width) { width = m_width; } /* don't allow upscaling */
416         if (height < 1 || height > m_height) { height = floor(width / m_aspect); }
417
418         if (bitrate == 0) {
419                 const double bitperpixel = .7; /* avg quality */
420                 bitrate = floor(m_fps * width * height * bitperpixel / 10000.0);
421         } else {
422                 bitrate = bitrate / 10;
423         }
424         if (bitrate < 10)  bitrate = 10;
425         if (bitrate > 1000) bitrate = 1000;
426
427         argp=(char**) calloc(16,sizeof(char*));
428         argp[0] = strdup(ffmpeg_exe.c_str());
429         argp[1] = strdup("-i");
430         argp[2] = strdup(infile.c_str());
431         argp[3] = strdup("-b:v");
432         argp[4] = (char*) calloc(7,sizeof(char)); snprintf(argp[4], 7, "%i0k", bitrate);
433         argp[5] = strdup("-s");
434         argp[6] = (char*) calloc(10,sizeof(char)); snprintf(argp[6], 10, "%ix%i", width, height);
435         argp[7] = strdup("-y");
436         argp[8] = strdup("-vcodec");
437         argp[9] = strdup("mjpeg");
438         argp[10] = strdup("-an");
439         argp[11] = strdup("-intra");
440         argp[12] = strdup("-g");
441         argp[13] = strdup("1");
442         argp[14] = strdup(outfile.c_str());
443         argp[15] = (char *)0;
444         /* Note: these are free()d in ~SystemExec */
445 #if 1 /* DEBUG */
446         if (debug_enable) { /* tentative debug mode */
447         printf("TRANSCODE VIDEO:\n");
448         for (int i=0; i< 15; ++i) {
449           printf("%s ", argp[i]);
450         }
451         printf("\n");
452         }
453 #endif
454         ffcmd = new SystemExec(ffmpeg_exe, argp);
455         ffcmd->ReadStdout.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffmpegparse_v, this, _1 ,_2));
456         ffcmd->Terminated.connect_same_thread (*this, boost::bind (&TranscodeFfmpeg::ffexit, this));
457         if (ffcmd->start(2)) {
458                 ffexit();
459                 return false;
460         }
461         return true;
462 }
463
464 void
465 TranscodeFfmpeg::cancel ()
466 {
467         if (!ffcmd || !ffcmd->is_running()) { return;}
468         ffcmd->write_to_stdin("q");
469         sleep (1);
470         if (ffcmd) {
471           ffcmd->terminate();
472         }
473 }
474
475 void
476 TranscodeFfmpeg::ffexit ()
477 {
478         delete ffcmd;
479         ffcmd=0;
480         Finished(); /* EMIT SIGNAL */
481 }
482
483 void
484 TranscodeFfmpeg::ffprobeparse (std::string d, size_t /* s */)
485 {
486         ffoutput+=d;
487 }
488
489 void
490 TranscodeFfmpeg::ffmpegparse_a (std::string d, size_t /* s */)
491 {
492         const char *t;
493         int h,m,s; char f[7];
494         ARDOUR::framecnt_t p = -1;
495
496         if (!(t=strstr(d.c_str(), "time="))) { return; }
497
498         if (sscanf(t+5, "%d:%d:%d.%s",&h,&m,&s,f) == 4) {
499                 p = (ARDOUR::framecnt_t) floor( 100.0 * (
500                       h * 3600.0
501                     + m * 60.0
502                     + s * 1.0
503                     + atoi(f) / pow(10, strlen(f))
504                 ));
505                 p = p * m_fps / 100.0;
506                 if (p > m_duration ) { p = m_duration; }
507                 Progress(p, m_duration); /* EMIT SIGNAL */
508         } else {
509                 Progress(0, 0); /* EMIT SIGNAL */
510         }
511 }
512
513 void
514 TranscodeFfmpeg::ffmpegparse_v (std::string d, size_t /* s */)
515 {
516         if (strstr(d.c_str(), "ERROR") || strstr(d.c_str(), "Error") || strstr(d.c_str(), "error")) {
517                 PBD::warning << "ffmpeg-error: " << d << endmsg;
518         }
519         if (strncmp(d.c_str(), "frame=",6)) {
520 #if 1 /* DEBUG */
521                 if (debug_enable) {
522                         d.erase(d.find_last_not_of(" \t\r\n") + 1);
523                   printf("ffmpeg: '%s'\n", d.c_str());
524                 }
525 #endif
526                 return;
527         }
528         ARDOUR::framecnt_t f = atol(d.substr(6).c_str());
529         Progress(f, m_duration); /* EMIT SIGNAL */
530 }
531
532 #endif /* WITH_VIDEOTIMELINE */