1 /*
2  * Copyright (C) 2013-2017 Paul Davis <paul@linuxaudiosystems.com>
3  * Copyright (C) 2013-2019 Robin Gareus <robin@gareus.org>
4  * Copyright (C) 2015 André Nusser <andre.nusser@googlemail.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
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 
32 #include <gtkmm/filechooserdialog.h>
33 #include <gtkmm/stock.h>
34 
35 #include "pbd/gstdio_compat.h"
36 
37 #include "pbd/error.h"
38 #include "pbd/convert.h"
39 #include "gtkmm2ext/utils.h"
40 #include "ardour/session_directory.h"
41 #include "ardour/profile.h"
42 #include "ardour/template_utils.h"
43 #include "ardour/session.h"
44 #include "ardour_ui.h"
45 #include "gui_thread.h"
46 
47 #include "opts.h"
48 #include "transcode_video_dialog.h"
49 #include "utils_videotl.h"
50 #include "pbd/i18n.h"
51 
52 using namespace Gtk;
53 using namespace std;
54 using namespace PBD;
55 using namespace ARDOUR;
56 using namespace VideoUtils;
57 
TranscodeVideoDialog(Session * s,std::string infile)58 TranscodeVideoDialog::TranscodeVideoDialog (Session* s, std::string infile)
59 	: ArdourDialog (_("Transcode/Import Video File "))
60 	, infn (infile)
61 	, path_label (_("Output File:"), Gtk::ALIGN_LEFT)
62 	, browse_button (_("Browse"))
63 	, transcode_button (_("OK"))
64 	, abort_button (_("Abort"))
65 	, progress_label ()
66 	, aspect_checkbox (_("Height = "))
67 	, height_adjustment (128, 0, 1920, 1, 16, 0)
68 	, height_spinner (height_adjustment)
69 	, ltc_detect (_("Extract LTC from audio and align video"))
70 	, bitrate_checkbox (_("Manual Override"))
71 	, bitrate_adjustment (2000, 500, 10000, 10, 100, 0)
72 	, bitrate_spinner (bitrate_adjustment)
73 #if 1 /* tentative debug mode */
74 	, debug_checkbox (_("Debug Mode: Print ffmpeg command and output to stdout."))
75 #endif
76 {
77 	set_session (s);
78 
79 	transcoder = new TranscodeFfmpeg(infile);
80 	audiofile = "";
81 	pending_audio_extract = false;
82 	aborted = false;
83 
84 	set_name ("TranscodeVideoDialog");
85 	set_modal (true);
86 	set_skip_taskbar_hint (true);
87 	set_resizable (false);
88 
89 	Gtk::Label* l;
90 	vbox = manage (new VBox);
91 	VBox* options_box = manage (new VBox);
92 	HBox* path_hbox = manage (new HBox);
93 
94 	int w = 0, h = 0;
95 	m_aspect = 4.0/3.0;
96 	TranscodeFfmpeg::FFAudioStreams as; as.clear();
97 
98 	path_hbox->pack_start (path_label, false, false, 3);
99 	path_hbox->pack_start (path_entry, true, true, 3);
100 	path_hbox->pack_start (browse_button, false, false, 3);
101 
102 	path_entry.set_width_chars(38);
103 	height_spinner.set_sensitive(false);
104 	bitrate_spinner.set_sensitive(false);
105 
106 	std::string dstdir = video_dest_dir(_session->session_directory().video_path(), video_get_docroot(Config));
107 	std::string dstfn  = video_dest_file(dstdir, infile);
108 	path_entry.set_text (dstfn);
109 
110 	l = manage (new Label (_("<b>File Information</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
111 	l->set_use_markup ();
112 	options_box->pack_start (*l, false, true, 4);
113 
114 	bool ffok = false;
115 	if (!transcoder->ffexec_ok()) {
116 		l = manage (new Label (_("ffmpeg installation was not found. Video Import is not possible. See the Log window for more information."), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
117 		l->set_line_wrap();
118 		options_box->pack_start (*l, false, true, 4);
119 		aspect_checkbox.set_sensitive(false);
120 		bitrate_checkbox.set_sensitive(false);
121 	}
122 	else if (!transcoder->probe_ok()) {
123 		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));
124 		options_box->pack_start (*l, false, true, 4);
125 		aspect_checkbox.set_sensitive(false);
126 		bitrate_checkbox.set_sensitive(false);
127 	} else {
128 		w = transcoder->get_width();
129 		h = transcoder->get_height();
130 		as = transcoder->get_audio();
131 		m_aspect = transcoder->get_aspect();
132 
133 		if (w > 0 && h > 0 && transcoder->get_fps() > 0 && transcoder->get_duration() > 0) {
134 			ffok = true;
135 		}
136 
137 		Table* t = manage (new Table (4, 2));
138 		t->set_spacings (4);
139 		options_box->pack_start (*t, true, true, 4);
140 		l = manage (new Label (_("FPS:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
141 		t->attach (*l, 0, 1, 0, 1);
142 		l = manage (new Label (_("Duration:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
143 		t->attach (*l, 2, 3, 0, 1);
144 		l = manage (new Label (_("Codec:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
145 		t->attach (*l, 0, 1, 1, 2);
146 		l = manage (new Label (_("Geometry:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
147 		t->attach (*l, 2, 3, 1, 2);
148 
149 		std::ostringstream osstream;
150 		osstream << transcoder->get_fps();
151 		l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
152 		t->attach (*l, 1, 2, 0, 1);
153 
154 		osstream.str("");
155 		osstream << w << "x" << h;
156 		l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
157 		t->attach (*l, 3, 4, 1, 2);
158 
159 		osstream.str("");
160 		if (transcoder->get_duration() == 0 || transcoder->get_fps() == 0) {
161 			osstream << _("??");
162 		} else {
163 			unsigned long sec = transcoder->get_duration() / transcoder->get_fps();
164 			osstream << setfill('0') << setw(2);
165 			osstream << (sec / 3600) << ":";
166 			osstream << setfill('0') << setw(2);
167 			osstream << ((sec /60 )%60) << ":";
168 			osstream << setfill('0') << setw(2);
169 			osstream << (sec%60)  << ":";
170 			osstream << setfill('0') << setw(2);
171 			osstream << (transcoder->get_duration() % (int) floor(transcoder->get_fps()));
172 		}
173 		l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
174 		t->attach (*l, 3, 4, 0, 1);
175 
176 		osstream.str("");
177 		osstream << transcoder->get_codec();
178 		l = manage (new Label (osstream.str(), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
179 		t->attach (*l, 1, 2, 1, 2);
180 	}
181 
182 	l = manage (new Label (_("<b>Import Settings</b>"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
183 	l->set_use_markup ();
184 	options_box->pack_start (*l, false, true, 4);
185 
186 	if (ffok) {
187 		video_combo.append_text(_("Reference from Current Location (Previously Transcoded Files Only)"));
188 		video_combo.append_text(_("Import/Transcode Video to Session"));
189 		video_combo.set_active(1);
190 		if (as.size() > 0) {
191 			video_combo.append_text(_("Do Not Import Video (Audio Import Only)"));
192 			audio_combo.set_sensitive(true);
193 		} else {
194 			audio_combo.set_sensitive(false);
195 		}
196 		video_combo.set_sensitive(true);
197 		transcode_button.set_sensitive(true);
198 		path_entry.set_sensitive (true);
199 		browse_button.set_sensitive (true);
200 	} else if (as.size() > 0) {
201 		video_combo.append_text(_("Do Not Import Video (Audio Import Only)"));
202 		video_combo.set_active(0);
203 		path_entry.set_text ("");
204 
205 		video_combo.set_sensitive(false);
206 		audio_combo.set_sensitive(true);
207 		transcode_button.set_sensitive(true);
208 		path_entry.set_sensitive (false);
209 		browse_button.set_sensitive (false);
210 	} else {
211 		video_combo.append_text(_("Do Not Import Video"));
212 		video_combo.set_active(0);
213 		path_entry.set_text ("");
214 		video_combo.set_sensitive(false);
215 		audio_combo.set_sensitive(false);
216 		transcode_button.set_sensitive(false);
217 		path_entry.set_sensitive (false);
218 		browse_button.set_sensitive (false);
219 	}
220 
221 	options_box->pack_start (video_combo, false, false, 4);
222 
223 	Table* t = manage (new Table (4, 4));
224 	t->set_spacings (4);
225 	options_box->pack_start (*t, true, true, 4);
226 
227 	l = manage (new Label (_("Scale Video: Width = "), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
228 	t->attach (*l, 0, 1, 0, 1);
229 	t->attach (scale_combo, 1, 2, 0, 1);
230 	t->attach (aspect_checkbox, 2, 3, 0, 1);
231 	t->attach (height_spinner, 3, 4, 0, 1);
232 
233 	scale_combo.append_text(_("Original Width"));
234 	if (w > 1920) { scale_combo.append_text("1920 (hd1080)"); }
235 	if (w > 1408) { scale_combo.append_text("1408 (16cif)"); }
236 	if (w > 1280) { scale_combo.append_text("1280 (sxga, hd720)"); }
237 	if (w > 1024) { scale_combo.append_text("1024 (xga)"); }
238 	if (w > 852)  { scale_combo.append_text(" 852 (hd480)"); }
239 	if (w > 768)  { scale_combo.append_text(" 768 (PAL)"); }
240 	if (w > 720)  { scale_combo.append_text(" 720 (PAL)"); }
241 	if (w > 640)  { scale_combo.append_text(" 640 (vga, ega)"); }
242 	if (w > 352)  { scale_combo.append_text(" 352 (cif)"); }
243 	if (w > 320)  { scale_combo.append_text(" 320 (cga, qvga)"); }
244 	if (w > 176)  { scale_combo.append_text(" 176 (qcif)"); }
245 	scale_combo.set_active(0);
246 	height_spinner.set_value(h);
247 
248 	l = manage (new Label (_("Bitrate (KBit/s):"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
249 	t->attach (*l, 0, 1, 1, 2);
250 	t->attach (bitrate_checkbox, 2, 3, 1, 2);
251 	t->attach (bitrate_spinner, 3, 4, 1, 2);
252 
253 	l = manage (new Label (_("Extract Audio:"), Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER, false));
254 	t->attach (*l, 0, 1, 2, 3);
255 	t->attach (audio_combo, 1, 4, 2, 3);
256 	t->attach (ltc_detect, 1, 4, 3, 4);
257 	if (as.size() == 0) {
258 		audio_combo.append_text(_("No Audio Track Present"));
259 		audio_combo.set_sensitive(false);
260 	} else {
261 		audio_combo.append_text(_("Do Not Extract Audio"));
262 		for (TranscodeFfmpeg::FFAudioStreams::iterator it = as.begin(); it < as.end(); ++it) {
263 			audio_combo.append_text((*it).name);
264 		}
265 	}
266 	audio_combo.set_active(0);
267 	ltc_detect.set_sensitive (false);
268 
269 #if 1 /* tentative debug mode */
270 	options_box->pack_start (debug_checkbox, false, true, 4);
271 #endif
272 
273 	vbox->pack_start (*path_hbox, false, false);
274 	vbox->pack_start (*options_box, false, true);
275 
276 	get_vbox()->set_spacing (4);
277 	get_vbox()->pack_start (*vbox, false, false);
278 
279 	progress_box = manage (new VBox);
280 	progress_box->set_spacing(6);
281 	progress_box->pack_start (progress_label, false, false);
282 	progress_box->pack_start (pbar, false, false);
283 	progress_box->pack_start (abort_button, false, false);
284 	get_vbox()->pack_start (*progress_box, false, false);
285 
286 	browse_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::open_browse_dialog));
287 	transcode_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::launch_transcode));
288 	abort_button.signal_clicked().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::abort_clicked));
289 
290 	video_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::video_combo_changed));
291 	audio_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::audio_combo_changed));
292 	scale_combo.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::scale_combo_changed));
293 	aspect_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::aspect_checkbox_toggled));
294 	height_spinner.signal_changed().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::update_bitrate));
295 	bitrate_checkbox.signal_toggled().connect (sigc::mem_fun (*this, &TranscodeVideoDialog::bitrate_checkbox_toggled));
296 
297 	update_bitrate();
298 
299 	cancel_button = add_button (Stock::CANCEL, RESPONSE_CANCEL);
300 	get_action_area()->pack_start (transcode_button, false, false);
301 	show_all_children ();
302 	progress_box->hide();
303 }
304 
~TranscodeVideoDialog()305 TranscodeVideoDialog::~TranscodeVideoDialog ()
306 {
307 	delete transcoder;
308 }
309 
310 void
on_show()311 TranscodeVideoDialog::on_show ()
312 {
313 	Dialog::on_show ();
314 }
315 
316 void
abort_clicked()317 TranscodeVideoDialog::abort_clicked ()
318 {
319 	aborted = true;
320 	transcoder->cancel();
321 }
322 
323 void
update_progress(samplecnt_t c,samplecnt_t a)324 TranscodeVideoDialog::update_progress (samplecnt_t c, samplecnt_t a)
325 {
326 	if (a == 0 || c > a) {
327 		pbar.set_pulse_step(.5);
328 		pbar.pulse();
329 		return;
330 	}
331 	pbar.set_fraction ((double)c / (double) a);
332 }
333 
334 void
finished()335 TranscodeVideoDialog::finished ()
336 {
337 	if (aborted) {
338 		::g_unlink(path_entry.get_text().c_str());
339 		if (!audiofile.empty()) {
340 			::g_unlink(audiofile.c_str());
341 		}
342 		Gtk::Dialog::response(RESPONSE_CANCEL);
343 	} else {
344 		if (pending_audio_extract) {
345 			StartNextStage();
346 		} else {
347 		  Gtk::Dialog::response(RESPONSE_ACCEPT);
348 		}
349 	}
350 }
351 
352 void
launch_audioonly()353 TranscodeVideoDialog::launch_audioonly ()
354 {
355 	if (audio_combo.get_active_row_number() == 0) {
356 		finished();
357 		return;
358 	}
359 	dialog_progress_mode();
360 #if 1 /* tentative debug mode */
361 	if (debug_checkbox.get_active()) {
362 		transcoder->set_debug(true);
363 	}
364 #endif
365 	transcoder->Progress.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::update_progress , this, _1, _2), gui_context());
366 	transcoder->Finished.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::finished, this), gui_context());
367 	launch_extract();
368 }
369 
370 void
launch_extract()371 TranscodeVideoDialog::launch_extract ()
372 {
373 	audiofile= path_entry.get_text() + ".wav"; /* TODO: mktemp */
374 	int audio_stream;
375 	pending_audio_extract = false;
376 	aborted = false;
377 	audio_stream = audio_combo.get_active_row_number() -1;
378 	progress_label.set_text (_("Extracting Audio.."));
379 
380 	if (!transcoder->extract_audio(audiofile, _session->nominal_sample_rate(), audio_stream)) {
381 		ARDOUR_UI::instance()->popup_error(_("Audio Extraction Failed."));
382 		audiofile="";
383 		Gtk::Dialog::response(RESPONSE_CANCEL);
384 		return;
385 	}
386 }
387 
388 void
dialog_progress_mode()389 TranscodeVideoDialog::dialog_progress_mode ()
390 {
391 	vbox->hide();
392 	cancel_button->hide();
393 	transcode_button.hide();
394 	pbar.set_size_request(300,-1);
395 	progress_box->show();
396 }
397 
398 void
launch_transcode()399 TranscodeVideoDialog::launch_transcode ()
400 {
401 	if (video_combo.get_active_row_number() != 1) {
402 		launch_audioonly();
403 		return;
404 	}
405 	std::string outfn = path_entry.get_text();
406 	if (!confirm_video_outfn(*this, outfn, video_get_docroot(Config))) return;
407 	progress_label.set_text (_("Transcoding Video.."));
408 	dialog_progress_mode();
409 #if 1 /* tentative debug mode */
410 	if (debug_checkbox.get_active()) {
411 		transcoder->set_debug(true);
412 	}
413 #endif
414 
415 	aborted = false;
416 	if (audio_combo.get_active_row_number() != 0) {
417 		pending_audio_extract = true;
418 		StartNextStage.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::launch_extract , this), gui_context());
419 	}
420 
421 	int scale_width, scale_height, bitrate;
422 	if (scale_combo.get_active_row_number() == 0 ) {
423 		scale_width =0;
424 	} else {
425 	  scale_width = atoi(scale_combo.get_active_text());
426 	}
427 	if (!aspect_checkbox.get_active()) {
428 		scale_height = 0;
429 	} else {
430 		scale_height = (int) floor(height_spinner.get_value());
431 	}
432 	if (bitrate_checkbox.get_active() ){
433 		bitrate = (int) floor(bitrate_spinner.get_value());
434 	} else {
435 		bitrate = 0;
436 	}
437 
438 	transcoder->Progress.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::update_progress , this, _1, _2), gui_context());
439 	transcoder->Finished.connect(*this, invalidator (*this), boost::bind (&TranscodeVideoDialog::finished, this), gui_context());
440 	if (!transcoder->transcode(outfn, scale_width, scale_height, bitrate)) {
441 		ARDOUR_UI::instance()->popup_error(_("Transcoding Failed."));
442 		Gtk::Dialog::response(RESPONSE_CANCEL);
443 		return;
444 	}
445 }
446 
447 void
video_combo_changed()448 TranscodeVideoDialog::video_combo_changed ()
449 {
450 	const int i = video_combo.get_active_row_number();
451 	if (i != 1) {
452 		scale_combo.set_sensitive(false);
453 		aspect_checkbox.set_sensitive(false);
454 		height_spinner.set_sensitive(false);
455 		bitrate_checkbox.set_sensitive(false);
456 		bitrate_spinner.set_sensitive(false);
457 	} else {
458 		scale_combo.set_sensitive(true);
459 		aspect_checkbox.set_sensitive(true);
460 		height_spinner.set_sensitive(true);
461 		bitrate_checkbox.set_sensitive(true);
462 		bitrate_spinner.set_sensitive(true);
463 	}
464 	if (i == 2 && audio_combo.get_active_row_number() == 0) {
465 		audio_combo.set_active(1);
466 	} else {
467 		//update LTC option sensitivity
468 		audio_combo_changed ();
469 	}
470 }
471 
472 void
audio_combo_changed()473 TranscodeVideoDialog::audio_combo_changed ()
474 {
475 	if (video_combo.get_active_row_number() == 2
476 			&& audio_combo.get_active_row_number() == 0)
477 	{
478 		audio_combo.set_active(1);
479 		ltc_detect.set_sensitive (false);
480 		ltc_detect.set_active (false);
481 	}
482 
483 	if (video_combo.get_active_row_number() != 2
484 			&& audio_combo.get_active_row_number() > 0)
485 	{
486 		ltc_detect.set_sensitive (true);
487 	} else {
488 		ltc_detect.set_sensitive (false);
489 		ltc_detect.set_active (false);
490 	}
491 }
492 
493 void
scale_combo_changed()494 TranscodeVideoDialog::scale_combo_changed ()
495 {
496 	if (!aspect_checkbox.get_active()) {
497 		int h;
498 		if (scale_combo.get_active_row_number() == 0 ) {
499 			h = transcoder->get_height();
500 		} else {
501 			h = floor(atof(scale_combo.get_active_text()) / m_aspect);
502 		}
503 		height_spinner.set_value(h);
504 	}
505 	update_bitrate();
506 }
507 
508 void
aspect_checkbox_toggled()509 TranscodeVideoDialog::aspect_checkbox_toggled ()
510 {
511 	height_spinner.set_sensitive(aspect_checkbox.get_active());
512 	scale_combo_changed();
513 }
514 
515 void
bitrate_checkbox_toggled()516 TranscodeVideoDialog::bitrate_checkbox_toggled ()
517 {
518 	bitrate_spinner.set_sensitive(bitrate_checkbox.get_active());
519 	if (!bitrate_checkbox.get_active()) {
520 		update_bitrate();
521 	}
522 }
523 
524 void
update_bitrate()525 TranscodeVideoDialog::update_bitrate ()
526 {
527 	double br = .7; /* avg quality - bits per pixel */
528 	if (bitrate_checkbox.get_active() || !transcoder->probe_ok()) { return; }
529 	br *= transcoder->get_fps();
530 	br *= height_spinner.get_value();
531 
532 	if (scale_combo.get_active_row_number() == 0 ) {
533 		br *= transcoder->get_width();
534 	} else {
535 		br *= atof(scale_combo.get_active_text());
536 	}
537 	if (br != 0) {
538 		bitrate_spinner.set_value(floor(br/10000.0)*10);
539 	}
540 }
541 
542 void
open_browse_dialog()543 TranscodeVideoDialog::open_browse_dialog ()
544 {
545 	Gtk::FileChooserDialog dialog(_("Save Transcoded Video File"), Gtk::FILE_CHOOSER_ACTION_SAVE);
546 	dialog.set_filename (path_entry.get_text());
547 	Gtkmm2ext::add_volume_shortcuts (dialog);
548 
549 	dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
550 	dialog.add_button(Gtk::Stock::OK, Gtk::RESPONSE_OK);
551 
552 	int result = dialog.run();
553 
554 	if (result == Gtk::RESPONSE_OK) {
555 		std::string filename = dialog.get_filename();
556 
557 		if (filename.length()) {
558 			path_entry.set_text (filename);
559 		}
560 	}
561 }
562 
563 enum VtlTranscodeOption
import_option()564 TranscodeVideoDialog::import_option() {
565 	int i = video_combo.get_active_row_number();
566 	return static_cast<VtlTranscodeOption>(i);
567 }
568