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