NOOP - use PBD's std::string to number functions
[ardour.git] / gtk2_ardour / transcode_video_dialog.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 <cstdio>
21 #include <string>
22 #include <sstream>
23 #include <iomanip>
24
25 #include <unistd.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29
30 #include <sigc++/bind.h>
31 #include <libgen.h>
32
33 #include "pbd/error.h"
34 #include "pbd/convert.h"
35 #include "gtkmm2ext/utils.h"
36 #include "ardour/session_directory.h"
37 #include "ardour/profile.h"
38 #include "ardour/template_utils.h"
39 #include "ardour/session.h"
40 #include "ardour_ui.h"
41 #include "gui_thread.h"
42
43 #include "utils.h"
44 #include "opts.h"
45 #include "transcode_video_dialog.h"
46 #include "utils_videotl.h"
47 #include "i18n.h"
48
49 using namespace Gtk;
50 using namespace std;
51 using namespace PBD;
52 using namespace ARDOUR;
53
54 TranscodeVideoDialog::TranscodeVideoDialog (Session* s, std::string infile)
55         : ArdourDialog (_("Transcode/Import Video File "))
56         , infn (infile)
57         , path_label (_("Output File:"), Gtk::ALIGN_LEFT)
58         , browse_button (_("Browse"))
59         , transcode_button (_("OK"))
60         , abort_button (_("Abort"))
61         , progress_label ()
62         , aspect_checkbox (_("Height = "))
63         , height_adjustment (128, 0, 1920, 1, 16, 0)
64         , height_spinner (height_adjustment)
65         , bitrate_checkbox (_("Manual Override"))
66         , bitrate_adjustment (2000, 500, 10000, 10, 100, 0)
67         , bitrate_spinner (bitrate_adjustment)
68 #if 1 /* tentative debug mode */
69         , debug_checkbox (_("Debug Mode: Print ffmpeg Command and Output to stdout."))
70 #endif
71 {
72         set_session (s);
73
74         transcoder = new TranscodeFfmpeg(infile);
75         audiofile = "";
76         pending_audio_extract = false;
77         aborted = false;
78
79         set_name ("TranscodeVideoDialog");
80         set_modal (true);
81         set_skip_taskbar_hint (true);
82         set_resizable (false);
83
84         Gtk::Label* l;
85         vbox = manage (new VBox);
86         VBox* options_box = manage (new VBox);
87         HBox* path_hbox = manage (new HBox);
88
89         int w = 0, h = 0;
90         m_aspect = 4.0/3.0;
91         AudioStreams as; as.clear();
92
93         path_hbox->pack_start (path_label, false, false, 3);
94         path_hbox->pack_start (path_entry, true, true, 3);
95         path_hbox->pack_start (browse_button, false, false, 3);
96         browse_button.set_name ("PaddedButton");
97
98         path_entry.set_width_chars(38);
99         height_spinner.set_sensitive(false);
100         bitrate_spinner.set_sensitive(false);
101
102         std::string dstdir = video_dest_dir(_session->session_directory().video_path(), video_get_docroot(Config));
103         std::string dstfn  = video_dest_file(dstdir, infile);
104         path_entry.set_text (dstfn);
105
106         l = manage (new Label (_("<b>File Information</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
107         l->set_use_markup ();
108         options_box->pack_start (*l, false, true, 4);
109
110         bool ffok = false;
111         if (!transcoder->ffexec_ok()) {
112                 l = manage (new Label (_("No ffprobe or ffmpeg executables could be found on this system. Video Import is not possible until you install those tools. See the Log widow for more information."), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
113                 l->set_line_wrap();
114                 options_box->pack_start (*l, false, true, 4);
115                 aspect_checkbox.set_sensitive(false);
116                 bitrate_checkbox.set_sensitive(false);
117         }
118         else if (!transcoder->probe_ok()) {
119                 l = manage (new Label (string_compose(_("File-info can not be read. Most likely '%1' is not a valid video-file or an unsupported video codec or format."), infn), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
120                 options_box->pack_start (*l, false, true, 4);
121                 aspect_checkbox.set_sensitive(false);
122                 bitrate_checkbox.set_sensitive(false);
123         } else {
124                 ffok = true;
125                 w = transcoder->get_width();
126                 h = transcoder->get_height();
127                 as = transcoder->get_audio();
128                 m_aspect = transcoder->get_aspect();
129
130                 Table* t = manage (new Table (4, 2));
131                 t->set_spacings (4);
132                 options_box->pack_start (*t, true, true, 4);
133                 l = manage (new Label (_("FPS:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
134                 t->attach (*l, 0, 1, 0, 1);
135                 l = manage (new Label (_("Duration:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
136                 t->attach (*l, 2, 3, 0, 1);
137                 l = manage (new Label (_("Codec:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
138                 t->attach (*l, 0, 1, 1, 2);
139                 l = manage (new Label (_("Geometry:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
140                 t->attach (*l, 2, 3, 1, 2);
141
142                 std::ostringstream osstream;
143                 osstream << transcoder->get_fps();
144                 l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
145                 t->attach (*l, 1, 2, 0, 1);
146
147                 osstream.str("");
148                 osstream << w << "x" << h;
149                 l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
150                 t->attach (*l, 3, 4, 1, 2);
151
152                 osstream.str("");
153                 if (transcoder->get_duration() == 0 || transcoder->get_fps() == 0) {
154                         osstream << _("??");
155                 } else {
156                         unsigned long sec = transcoder->get_duration() / transcoder->get_fps();
157                         osstream << setfill('0') << setw(2);
158                         osstream << (sec / 3600) << ":";
159                         osstream << setfill('0') << setw(2);
160                         osstream << ((sec /60 )%60) << ":";
161                         osstream << setfill('0') << setw(2);
162                         osstream << (sec%60)  << ":";
163                         osstream << setfill('0') << setw(2);
164                         osstream << (transcoder->get_duration() % (int) floor(transcoder->get_fps()));
165                 }
166                 l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
167                 t->attach (*l, 3, 4, 0, 1);
168
169                 osstream.str("");
170                 osstream << transcoder->get_codec();
171                 l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
172                 t->attach (*l, 1, 2, 1, 2);
173         }
174
175         l = manage (new Label (_("<b>Import Settings</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
176         l->set_use_markup ();
177         options_box->pack_start (*l, false, true, 4);
178
179         video_combo.set_name ("PaddedButton");
180         video_combo.append_text(_("Do Not Import Video"));
181         video_combo.append_text(_("Reference From Current Location"));
182         if (ffok)  {
183                 video_combo.append_text(_("Import/Transcode Video to Session"));
184                 video_combo.set_active(2);
185         } else {
186                 video_combo.set_active(1);
187                 video_combo.set_sensitive(false);
188                 audio_combo.set_sensitive(false);
189         }
190
191         options_box->pack_start (video_combo, false, false, 4);
192
193         Table* t = manage (new Table (4, 3));
194         t->set_spacings (4);
195         options_box->pack_start (*t, true, true, 4);
196
197         l = manage (new Label (_("Scale Video: Width = "), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
198         t->attach (*l, 0, 1, 0, 1);
199         scale_combo.set_name ("PaddedButton");
200         t->attach (scale_combo, 1, 2, 0, 1);
201         t->attach (aspect_checkbox, 2, 3, 0, 1);
202         t->attach (height_spinner, 3, 4, 0, 1);
203
204         scale_combo.append_text(_("Original Width"));
205         if (w > 1920) { scale_combo.append_text("1920 (hd1080)"); }
206         if (w > 1408) { scale_combo.append_text("1408 (16cif)"); }
207         if (w > 1280) { scale_combo.append_text("1280 (sxga, hd720)"); }
208         if (w > 1024) { scale_combo.append_text("1024 (xga)"); }
209         if (w > 852)  { scale_combo.append_text(" 852 (hd480)"); }
210         if (w > 768)  { scale_combo.append_text(" 768 (PAL)"); }
211         if (w > 720)  { scale_combo.append_text(" 720 (PAL)"); }
212         if (w > 640)  { scale_combo.append_text(" 640 (vga, ega)"); }
213         if (w > 352)  { scale_combo.append_text(" 352 (cif)"); }
214         if (w > 320)  { scale_combo.append_text(" 320 (cga, qvga)"); }
215         if (w > 176)  { scale_combo.append_text(" 176 (qcif)"); }
216         scale_combo.set_active(0);
217         height_spinner.set_value(h);
218
219         l = manage (new Label (_("Bitrate (KBit/s):"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
220         t->attach (*l, 0, 1, 1, 2);
221         t->attach (bitrate_checkbox, 2, 3, 1, 2);
222         t->attach (bitrate_spinner, 3, 4, 1, 2);
223
224         l = manage (new Label (_("Extract Audio:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
225         t->attach (*l, 0, 1, 2, 3);
226         audio_combo.set_name ("PaddedButton");
227         t->attach (audio_combo, 1, 4, 2, 3);
228         audio_combo.append_text("No audio");
229         if (as.size() > 0) {
230                 for (AudioStreams::iterator it = as.begin(); it < as.end(); ++it) {
231                         audio_combo.append_text((*it).name);
232                 }
233         }
234         audio_combo.set_active(0);
235
236 #if 1 /* tentative debug mode */
237         options_box->pack_start (debug_checkbox, false, true, 4);
238 #endif
239
240         vbox->pack_start (*path_hbox, false, false);
241         vbox->pack_start (*options_box, false, true);
242
243         get_vbox()->set_spacing (4);
244         get_vbox()->pack_start (*vbox, false, false);
245
246         progress_box = manage (new VBox);
247         progress_box->pack_start (progress_label, false, false);
248         progress_box->pack_start (pbar, false, false);
249         progress_box->pack_start (abort_button, false, false);
250         get_vbox()->pack_start (*progress_box, false, false);
251
252         browse_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::open_browse_dialog));
253         transcode_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::launch_transcode));
254         abort_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::abort_clicked));
255
256         video_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::video_combo_changed));
257         audio_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::audio_combo_changed));
258         scale_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::scale_combo_changed));
259         aspect_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::aspect_checkbox_toggled));
260         height_spinner.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::update_bitrate));
261         bitrate_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::bitrate_checkbox_toggled));
262
263         update_bitrate();
264
265         cancel_button = add_button (Stock::CANCEL, RESPONSE_CANCEL);
266         get_action_area()->pack_start (transcode_button, false, false);
267         show_all_children ();
268         progress_box->hide();
269 }
270
271 TranscodeVideoDialog::~TranscodeVideoDialog ()
272 {
273         delete transcoder;
274 }
275
276 void
277 TranscodeVideoDialog::on_show ()
278 {
279         Dialog::on_show ();
280 }
281
282 void
283 TranscodeVideoDialog::abort_clicked ()
284 {
285         aborted = true;
286         transcoder->cancel();
287 }
288
289 void
290 TranscodeVideoDialog::update_progress (framecnt_t c, framecnt_t a)
291 {
292         if (a == 0 || c > a) {
293                 pbar.set_pulse_step(.5);
294                 pbar.pulse();
295                 return;
296         }
297         pbar.set_fraction ((double)c / (double) a);
298 }
299
300 void
301 TranscodeVideoDialog::finished ()
302 {
303         if (aborted) {
304                 unlink(path_entry.get_text().c_str());
305                 if (!audiofile.empty()) {
306                         unlink(audiofile.c_str());
307                 }
308                 Gtk::Dialog::response(RESPONSE_CANCEL);
309         } else {
310                 if (pending_audio_extract) {
311                         StartNextStage();
312                 } else {
313                   Gtk::Dialog::response(RESPONSE_ACCEPT);
314                 }
315         }
316 }
317
318 void
319 TranscodeVideoDialog::launch_audioonly ()
320 {
321         if (audio_combo.get_active_row_number() == 0) {
322                 finished();
323                 return;
324         }
325         dialog_progress_mode();
326 #if 1 /* tentative debug mode */
327         if (debug_checkbox.get_active()) {
328                 transcoder->set_debug(true);
329         }
330 #endif
331         transcoder->Progress.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::update_progress , this, _1, _2), gui_context());
332         transcoder->Finished.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::finished, this), gui_context());
333         launch_extract();
334 }
335
336 void
337 TranscodeVideoDialog::launch_extract ()
338 {
339         audiofile= path_entry.get_text() + ".wav"; /* TODO: mktemp */
340         int audio_stream;
341         pending_audio_extract = false;
342         aborted = false;
343         audio_stream = audio_combo.get_active_row_number() -1;
344         progress_label.set_text (_("Extracting Audio.."));
345
346         if (!transcoder->extract_audio(audiofile, _session->nominal_frame_rate(), audio_stream)) {
347                 ARDOUR_UI::instance()->popup_error(_("Audio Extraction Failed."));
348                 audiofile="";
349                 Gtk::Dialog::response(RESPONSE_CANCEL);
350                 return;
351         }
352 }
353
354 void
355 TranscodeVideoDialog::dialog_progress_mode ()
356 {
357         vbox->hide();
358         cancel_button->hide();
359         transcode_button.hide();
360         pbar.set_size_request(300,-1);
361         progress_box->show();
362 }
363
364 void
365 TranscodeVideoDialog::launch_transcode ()
366 {
367         if (video_combo.get_active_row_number() != 2) {
368                 launch_audioonly();
369                 return;
370         }
371         std::string outfn = path_entry.get_text();
372         if (!confirm_video_outfn(outfn, video_get_docroot(Config))) return;
373         progress_label.set_text (_("Transcoding Video.."));
374         dialog_progress_mode();
375 #if 1 /* tentative debug mode */
376         if (debug_checkbox.get_active()) {
377                 transcoder->set_debug(true);
378         }
379 #endif
380
381         aborted = false;
382         if (audio_combo.get_active_row_number() != 0) {
383                 pending_audio_extract = true;
384                 StartNextStage.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::launch_extract , this), gui_context());
385         }
386
387         int scale_width, scale_height, bitrate;
388         if (scale_combo.get_active_row_number() == 0 ) {
389                 scale_width =0;
390         } else {
391           scale_width = atoi(scale_combo.get_active_text());
392         }
393         if (!aspect_checkbox.get_active()) {
394                 scale_height = 0;
395         } else {
396                 scale_height = (int) floor(height_spinner.get_value());
397         }
398         if (bitrate_checkbox.get_active() ){
399                 bitrate = (int) floor(bitrate_spinner.get_value());
400         } else {
401                 bitrate = 0;
402         }
403
404         transcoder->Progress.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::update_progress , this, _1, _2), gui_context());
405         transcoder->Finished.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::finished, this), gui_context());
406         if (!transcoder->transcode(outfn, scale_width, scale_height, bitrate)) {
407                 ARDOUR_UI::instance()->popup_error(_("Transcoding Failed."));
408                 Gtk::Dialog::response(RESPONSE_CANCEL);
409                 return;
410         }
411 }
412
413 void
414 TranscodeVideoDialog::video_combo_changed ()
415 {
416         int i = video_combo.get_active_row_number();
417         if (i != 2) {
418                 scale_combo.set_sensitive(false);
419                 aspect_checkbox.set_sensitive(false);
420                 height_spinner.set_sensitive(false);
421                 bitrate_checkbox.set_sensitive(false);
422                 bitrate_spinner.set_sensitive(false);
423         } else {
424                 scale_combo.set_sensitive(true);
425                 aspect_checkbox.set_sensitive(true);
426                 height_spinner.set_sensitive(true);
427                 bitrate_checkbox.set_sensitive(true);
428                 bitrate_spinner.set_sensitive(true);
429         }
430 }
431
432 void
433 TranscodeVideoDialog::audio_combo_changed ()
434 {
435         ;
436 }
437
438 void
439 TranscodeVideoDialog::scale_combo_changed ()
440 {
441         if (!aspect_checkbox.get_active()) {
442                 int h;
443                 if (scale_combo.get_active_row_number() == 0 ) {
444                         h = transcoder->get_height();
445                 } else {
446                         h = floor(atof(scale_combo.get_active_text()) / m_aspect);
447                 }
448                 height_spinner.set_value(h);
449         }
450         update_bitrate();
451 }
452
453 void
454 TranscodeVideoDialog::aspect_checkbox_toggled ()
455 {
456         height_spinner.set_sensitive(aspect_checkbox.get_active());
457         scale_combo_changed();
458 }
459
460 void
461 TranscodeVideoDialog::bitrate_checkbox_toggled ()
462 {
463         bitrate_spinner.set_sensitive(bitrate_checkbox.get_active());
464         if (!bitrate_checkbox.get_active()) {
465                 update_bitrate();
466         }
467 }
468
469 void
470 TranscodeVideoDialog::update_bitrate ()
471 {
472         double br = .7; /* avg quality - bits per pixel */
473         if (bitrate_checkbox.get_active() || !transcoder->probe_ok()) { return; }
474         br *= transcoder->get_fps();
475         br *= height_spinner.get_value();
476
477         if (scale_combo.get_active_row_number() == 0 ) {
478                 br *= transcoder->get_width();
479         } else {
480                 br *= atof(scale_combo.get_active_text());
481         }
482         if (br != 0) {
483                 bitrate_spinner.set_value(floor(br/10000.0)*10);
484         }
485 }
486
487 void
488 TranscodeVideoDialog::open_browse_dialog ()
489 {
490         Gtk::FileChooserDialog dialog(_("Save Transcoded Video File"), Gtk::FILE_CHOOSER_ACTION_SAVE);
491         dialog.set_filename (path_entry.get_text());
492
493         dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
494         dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
495
496         int result = dialog.run();
497
498         if (result == Gtk::RESPONSE_OK) {
499                 std::string filename = dialog.get_filename();
500
501                 if (filename.length()) {
502                         path_entry.set_text (filename);
503                 }
504         }
505 }
506
507 enum VtlTranscodeOption
508 TranscodeVideoDialog::import_option() {
509         int i = video_combo.get_active_row_number();
510         return static_cast<VtlTranscodeOption>(i);
511 }