2 Copyright (C) 2010-2013 Paul Davis
3 Author: Robin Gareus <robin@gareus.org>
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.
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.
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.
20 #ifdef WITH_VIDEOTIMELINE
25 #include <sys/types.h>
27 #include "pbd/error.h"
28 #include "pbd/file_utils.h"
29 #include "pbd/file_utils.h"
30 #include "gui_thread.h"
32 #include "transcode_ffmpeg.h"
33 #include "utils_videotl.h"
37 TranscodeFfmpeg::TranscodeFfmpeg (std::string f)
45 #if 1 /* tentative debug mode */
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");
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");
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");
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");
66 if (ffmpeg_exe.empty() || ffprobe_exe.empty()) {
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"
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"
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"
83 if (infile.empty() || !probe()) {
89 TranscodeFfmpeg::~TranscodeFfmpeg ()
95 TranscodeFfmpeg::probe ()
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());
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)) {
118 std::vector<std::vector<std::string> > lines;
119 ParseCSV(ffoutput, lines);
121 m_width = m_height = 0;
122 m_fps = m_aspect = 0;
127 #define PARSE_FRACTIONAL_FPS(VAR) \
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()); \
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 */
141 if (i->at(0) == X_("stream")) {
142 if (i->at(5) == X_("codec_type=video") && m_width == 0) {
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);
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 += " ";
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 * (
176 + atoi(f) / pow(10, strlen(f))
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());
193 m_aspect = (double)m_width / (double)m_height;
196 } else if (i->at(5) == X_("codec_type=audio")) { /* new ffprobe */
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);
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 += " ";
211 } else if (key == X_("codec_name")) {
212 if (!as.name.empty()) as.name += " ";
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;
223 m_audio.push_back(as);
231 while (ffcmd && --timeout) usleep (1000); // wait until 'ffprobe' terminated.
232 if (timeout == 0) return false;
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);
245 printf("audio: no audio streams in file.\n");
253 TranscodeFfmpeg::default_encoder_settings ()
257 ffs["-vcodec"] = "mpeg4";
258 ffs["-acodec"] = "ac3";
259 ffs["-b:v"] = "5000k";
260 ffs["-b:a"] = "160k";
265 TranscodeFfmpeg::default_meta_data ()
269 ffm["comment"] = "Created with ardour";
274 TranscodeFfmpeg::format_metadata (std::string key, std::string value)
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)
282 v1.replace(start_pos, 1, "_");
287 while((start_pos = v1.find("\"", start_pos)) != std::string::npos) {
288 v1.replace(start_pos, 1, "\\\"");
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());
299 TranscodeFfmpeg::encode (std::string outfile, std::string inf_a, std::string inf_v, FFSettings ffs, FFSettings meta, bool map)
301 #define MAX_FFMPEG_ENCODER_ARGS (100)
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());
312 argp[a++] = strdup("-i");
313 argp[a++] = strdup(inf_v.c_str());
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());
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());
326 argp[a++] = strdup("-map");
327 argp[a++] = strdup("0:0");
328 argp[a++] = strdup("-map");
329 argp[a++] = strdup("1:0");
331 argp[a++] = strdup("-y");
332 argp[a++] = strdup(outfile.c_str());
334 assert(a<MAX_FFMPEG_ENCODER_ARGS);
335 /* Note: these are free()d in ~SystemExec */
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]);
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)) {
357 TranscodeFfmpeg::extract_audio (std::string outfile, ARDOUR::framecnt_t samplerate, unsigned int stream)
359 if (!probeok) return false;
360 if (stream >= m_audio.size()) return false;
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);
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 */
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]);
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)) {
406 TranscodeFfmpeg::transcode (std::string outfile, const int outw, const int outh, const int kbitps)
408 if (!probeok) return false;
411 int bitrate = kbitps;
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); }
419 const double bitperpixel = .7; /* avg quality */
420 bitrate = floor(m_fps * width * height * bitperpixel / 10000.0);
422 bitrate = bitrate / 10;
424 if (bitrate < 10) bitrate = 10;
425 if (bitrate > 1000) bitrate = 1000;
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 */
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]);
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)) {
465 TranscodeFfmpeg::cancel ()
467 if (!ffcmd || !ffcmd->is_running()) { return;}
468 ffcmd->write_to_stdin("q");
476 TranscodeFfmpeg::ffexit ()
480 Finished(); /* EMIT SIGNAL */
484 TranscodeFfmpeg::ffprobeparse (std::string d, size_t /* s */)
490 TranscodeFfmpeg::ffmpegparse_a (std::string d, size_t /* s */)
493 int h,m,s; char f[7];
494 ARDOUR::framecnt_t p = -1;
496 if (!(t=strstr(d.c_str(), "time="))) { return; }
498 if (sscanf(t+5, "%d:%d:%d.%s",&h,&m,&s,f) == 4) {
499 p = (ARDOUR::framecnt_t) floor( 100.0 * (
503 + atoi(f) / pow(10, strlen(f))
505 p = p * m_fps / 100.0;
506 if (p > m_duration ) { p = m_duration; }
507 Progress(p, m_duration); /* EMIT SIGNAL */
509 Progress(0, 0); /* EMIT SIGNAL */
514 TranscodeFfmpeg::ffmpegparse_v (std::string d, size_t /* s */)
516 if (strstr(d.c_str(), "ERROR") || strstr(d.c_str(), "Error") || strstr(d.c_str(), "error")) {
517 PBD::warning << "ffmpeg-error: " << d << endmsg;
519 if (strncmp(d.c_str(), "frame=",6)) {
522 d.erase(d.find_last_not_of(" \t\r\n") + 1);
523 printf("ffmpeg: '%s'\n", d.c_str());
528 ARDOUR::framecnt_t f = atol(d.substr(6).c_str());
529 Progress(f, m_duration); /* EMIT SIGNAL */
532 #endif /* WITH_VIDEOTIMELINE */