1 /*
2  * Copyright (C) 2005-2007 Doug McLain <doug@nostar.net>
3  * Copyright (C) 2005-2017 Tim Mayberry <mojofunk@gmail.com>
4  * Copyright (C) 2005-2019 Paul Davis <paul@linuxaudiosystems.com>
5  * Copyright (C) 2005 Karsten Wiese <fzuuzf@googlemail.com>
6  * Copyright (C) 2005 Taybin Rutkin <taybin@taybin.com>
7  * Copyright (C) 2006-2015 David Robillard <d@drobilla.net>
8  * Copyright (C) 2007-2012 Carl Hetherington <carl@carlh.net>
9  * Copyright (C) 2008-2010 Sakari Bergen <sakari.bergen@beatwaves.net>
10  * Copyright (C) 2012-2019 Robin Gareus <robin@gareus.org>
11  * Copyright (C) 2013-2015 Colin Fletcher <colin.m.fletcher@googlemail.com>
12  * Copyright (C) 2013-2016 John Emmas <john@creativepost.co.uk>
13  * Copyright (C) 2013-2016 Nick Mainsbridge <mainsbridge@gmail.com>
14  * Copyright (C) 2014-2018 Ben Loftis <ben@harrisonconsoles.com>
15  * Copyright (C) 2015 André Nusser <andre.nusser@googlemail.com>
16  * Copyright (C) 2016-2018 Len Ovens <len@ovenwerks.net>
17  * Copyright (C) 2017 Johannes Mueller <github@johannes-mueller.org>
18  *
19  * This program is free software; you can redistribute it and/or modify
20  * it under the terms of the GNU General Public License as published by
21  * the Free Software Foundation; either version 2 of the License, or
22  * (at your option) any later version.
23  *
24  * This program is distributed in the hope that it will be useful,
25  * but WITHOUT ANY WARRANTY; without even the implied warranty of
26  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27  * GNU General Public License for more details.
28  *
29  * You should have received a copy of the GNU General Public License along
30  * with this program; if not, write to the Free Software Foundation, Inc.,
31  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
32  */
33 
34 #ifdef WAF_BUILD
35 #include "gtk2ardour-config.h"
36 #include "gtk2ardour-version.h"
37 #endif
38 
39 #include "pbd/gstdio_compat.h"
40 
41 #include <gtkmm/stock.h>
42 
43 #include "pbd/error.h"
44 #include "pbd/openuri.h"
45 
46 #include "ardour/ltc_file_reader.h"
47 #include "ardour/session_directory.h"
48 
49 #include "add_video_dialog.h"
50 #include "ardour_ui.h"
51 #include "export_video_infobox.h"
52 #include "export_video_dialog.h"
53 #include "public_editor.h"
54 #include "utils_videotl.h"
55 #include "transcode_video_dialog.h"
56 #include "video_server_dialog.h"
57 
58 #include "pbd/i18n.h"
59 
60 using namespace ARDOUR;
61 using namespace PBD;
62 using namespace Gtk;
63 using namespace Gtkmm2ext;
64 using namespace std;
65 
66 void
stop_video_server(bool ask_confirm)67 ARDOUR_UI::stop_video_server (bool ask_confirm)
68 {
69 	if (!video_server_process && ask_confirm) {
70 		warning << string_compose (_("Video-Server was not launched by %1. The request to stop it is ignored."), PROGRAM_NAME) << endmsg;
71 	}
72 	if (video_server_process) {
73 		if(ask_confirm) {
74 			ArdourDialog confirm (_("Stop Video-Server"), true);
75 			Label m (_("Do you really want to stop the Video Server?"));
76 			confirm.get_vbox()->pack_start (m, true, true);
77 			confirm.add_button (Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
78 			confirm.add_button (_("Yes, Stop It"), Gtk::RESPONSE_ACCEPT);
79 			confirm.show_all ();
80 			switch (confirm.run()) {
81 				case RESPONSE_ACCEPT:
82 					break;
83 				default:
84 					return;
85 			}
86 		}
87 		delete video_server_process;
88 		video_server_process =0;
89 	}
90 }
91 
92 void
start_video_server_menu(Gtk::Window * float_window)93 ARDOUR_UI::start_video_server_menu (Gtk::Window* float_window)
94 {
95   ARDOUR_UI::start_video_server( float_window, true);
96 }
97 
98 bool
start_video_server(Gtk::Window * float_window,bool popup_msg)99 ARDOUR_UI::start_video_server (Gtk::Window* float_window, bool popup_msg)
100 {
101 	if (!_session) {
102 		return false;
103 	}
104 	if (popup_msg) {
105 		if (ARDOUR_UI::instance()->video_timeline->check_server()) {
106 			if (video_server_process) {
107 				popup_error(_("The Video Server is already started."));
108 			} else {
109 				popup_error(_("An external Video Server is configured and can be reached. Not starting a new instance."));
110 			}
111 		}
112 	}
113 
114 	int firsttime = 0;
115 	while (!ARDOUR_UI::instance()->video_timeline->check_server()) {
116 		if (firsttime++) {
117 			warning << _("Could not connect to the Video Server. Start it or configure its access URL in Preferences.") << endmsg;
118 		}
119 		VideoServerDialog *video_server_dialog = new VideoServerDialog (_session);
120 		if (float_window) {
121 			video_server_dialog->set_transient_for (*float_window);
122 		}
123 
124 		if (!Config->get_show_video_server_dialog() && firsttime < 2) {
125 			video_server_dialog->hide();
126 		} else {
127 			ResponseType r = (ResponseType) video_server_dialog->run ();
128 			video_server_dialog->hide();
129 			if (r != RESPONSE_ACCEPT) { return false; }
130 			if (video_server_dialog->show_again()) {
131 				Config->set_show_video_server_dialog(false);
132 			}
133 		}
134 
135 		std::string icsd_exec = video_server_dialog->get_exec_path();
136 		std::string icsd_docroot = video_server_dialog->get_docroot();
137 #ifndef PLATFORM_WINDOWS
138 		if (icsd_docroot.empty()) {
139 			icsd_docroot = VideoUtils::video_get_docroot (Config);
140 		}
141 #endif
142 
143 		GStatBuf sb;
144 #ifdef PLATFORM_WINDOWS
145 		if (VideoUtils::harvid_version >= 0x000802 && icsd_docroot.empty()) {
146 			/* OK, allow all drive letters */
147 		} else
148 #endif
149 		if (g_lstat (icsd_docroot.c_str(), &sb) != 0 || !S_ISDIR(sb.st_mode)) {
150 			warning << _("Specified docroot is not an existing directory.") << endmsg;
151 			continue;
152 		}
153 #ifndef PLATFORM_WINDOWS
154 		if ( (g_lstat (icsd_exec.c_str(), &sb) != 0)
155 		     || (sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) == 0 ) {
156 			warning << _("Given Video Server is not an executable file.") << endmsg;
157 			continue;
158 		}
159 #else
160 		if ( (g_lstat (icsd_exec.c_str(), &sb) != 0)
161 		     || (sb.st_mode & (S_IXUSR)) == 0 ) {
162 			warning << _("Given Video Server is not an executable file.") << endmsg;
163 			continue;
164 		}
165 #endif
166 
167 		char **argp;
168 		argp=(char**) calloc(9,sizeof(char*));
169 		argp[0] = strdup(icsd_exec.c_str());
170 		argp[1] = strdup("-P");
171 		argp[2] = (char*) calloc(16,sizeof(char)); snprintf(argp[2], 16, "%s", video_server_dialog->get_listenaddr().c_str());
172 		argp[3] = strdup("-p");
173 		argp[4] = (char*) calloc(6,sizeof(char)); snprintf(argp[4], 6, "%i", video_server_dialog->get_listenport());
174 		argp[5] = strdup("-C");
175 		argp[6] = (char*) calloc(6,sizeof(char)); snprintf(argp[6], 6, "%i", video_server_dialog->get_cachesize());
176 		argp[7] = strdup(icsd_docroot.c_str());
177 		argp[8] = 0;
178 		stop_video_server();
179 
180 #ifdef PLATFORM_WINDOWS
181 		if (VideoUtils::harvid_version >= 0x000802 && icsd_docroot.empty()) {
182 			/* OK, allow all drive letters */
183 		} else
184 #endif
185 		if (icsd_docroot == X_("/") || icsd_docroot == X_("C:\\")) {
186 			Config->set_video_advanced_setup(false);
187 		} else {
188 			std::string url_str = "http://127.0.0.1:" + to_string(video_server_dialog->get_listenport()) + "/";
189 			Config->set_video_server_url(url_str);
190 			Config->set_video_server_docroot(icsd_docroot);
191 			Config->set_video_advanced_setup(true);
192 		}
193 
194 		if (video_server_process) {
195 			delete video_server_process;
196 		}
197 
198 		video_server_process = new ARDOUR::SystemExec(icsd_exec, argp);
199 		if (video_server_process->start()) {
200 			warning << _("Cannot launch the video-server") << endmsg;
201 			continue;
202 		}
203 		int timeout = 120; // 6 sec
204 		while (!ARDOUR_UI::instance()->video_timeline->check_server()) {
205 			Glib::usleep (50000);
206 			gui_idle_handler();
207 			if (--timeout <= 0 || !video_server_process->is_running()) break;
208 		}
209 		if (timeout <= 0) {
210 			warning << _("Video-server was started but does not respond to requests...") << endmsg;
211 		} else {
212 			if (!ARDOUR_UI::instance()->video_timeline->check_server_docroot()) {
213 				delete video_server_process;
214 				video_server_process = 0;
215 			}
216 		}
217 	}
218 	return true;
219 }
220 
221 void
add_video(Gtk::Window * float_window)222 ARDOUR_UI::add_video (Gtk::Window* float_window)
223 {
224 	if (!_session) {
225 		return;
226 	}
227 
228 	if (!start_video_server(float_window, false)) {
229 		warning << _("Could not connect to the Video Server. Start it or configure its access URL in Preferences.") << endmsg;
230 		return;
231 	}
232 
233 	if (float_window) {
234 		add_video_dialog->set_transient_for (*float_window);
235 	}
236 
237 	if (add_video_dialog->is_visible()) {
238 		/* we're already doing this */
239 		return;
240 	}
241 
242 	ResponseType r = (ResponseType) add_video_dialog->run ();
243 	add_video_dialog->hide();
244 	if (r != RESPONSE_ACCEPT) { return; }
245 
246 	bool local_file, orig_local_file;
247 	std::string path = add_video_dialog->file_name(local_file);
248 
249 	std::string orig_path = path;
250 	orig_local_file = local_file;
251 
252 	bool auto_set_session_fps = add_video_dialog->auto_set_session_fps();
253 
254 	if (local_file && !Glib::file_test(path, Glib::FILE_TEST_EXISTS)) {
255 		warning << string_compose(_("could not open %1"), path) << endmsg;
256 		return;
257 	}
258 	if (!local_file && path.length() == 0) {
259 		warning << _("no video-file selected") << endmsg;
260 		return;
261 	}
262 
263 	std::string audio_from_video;
264 	bool detect_ltc = false;
265 
266 	switch (add_video_dialog->import_option()) {
267 		case VTL_IMPORT_TRANSCODE:
268 			{
269 				TranscodeVideoDialog *transcode_video_dialog;
270 				transcode_video_dialog = new TranscodeVideoDialog (_session, path);
271 				ResponseType r = (ResponseType) transcode_video_dialog->run ();
272 				transcode_video_dialog->hide();
273 				if (r != RESPONSE_ACCEPT) {
274 					delete transcode_video_dialog;
275 					return;
276 				}
277 
278 				audio_from_video = transcode_video_dialog->get_audiofile();
279 
280 				if (!audio_from_video.empty() && transcode_video_dialog->detect_ltc()) {
281 					detect_ltc = true;
282 				}
283 				else if (!audio_from_video.empty()) {
284 					editor->embed_audio_from_video(
285 							audio_from_video,
286 							video_timeline->get_offset(),
287 							(transcode_video_dialog->import_option() != VTL_IMPORT_NO_VIDEO)
288 							);
289 				}
290 				switch (transcode_video_dialog->import_option()) {
291 					case VTL_IMPORT_TRANSCODED:
292 						path = transcode_video_dialog->get_filename();
293 						local_file = true;
294 						break;
295 					case VTL_IMPORT_REFERENCE:
296 						break;
297 					default:
298 						delete transcode_video_dialog;
299 						return;
300 				}
301 				delete transcode_video_dialog;
302 			}
303 			break;
304 		default:
305 		case VTL_IMPORT_NONE:
306 			break;
307 	}
308 
309 	/* strip _session->session_directory().video_path() from video file if possible */
310 	if (local_file && !path.compare(0, _session->session_directory().video_path().size(), _session->session_directory().video_path())) {
311 		 path=path.substr(_session->session_directory().video_path().size());
312 		 if (path.at(0) == G_DIR_SEPARATOR) {
313 			 path=path.substr(1);
314 		 }
315 	}
316 
317 	video_timeline->set_update_session_fps(auto_set_session_fps);
318 
319 	if (video_timeline->video_file_info(path, local_file)) {
320 		XMLNode* node = new XMLNode(X_("Videotimeline"));
321 		node->set_property (X_("Filename"), path);
322 		node->set_property (X_("AutoFPS"), auto_set_session_fps);
323 		node->set_property (X_("LocalFile"), local_file);
324 		if (orig_local_file) {
325 			node->set_property (X_("OriginalVideoFile"), orig_path);
326 		} else {
327 			node->remove_property (X_("OriginalVideoFile"));
328 		}
329 		_session->add_extra_xml (*node);
330 		_session->set_dirty ();
331 
332 		if (!audio_from_video.empty() && detect_ltc) {
333 			std::vector<LTCFileReader::LTCMap> ltc_seq;
334 
335 			try {
336 				/* TODO ask user about TV standard (LTC alignment if any) */
337 				LTCFileReader ltcr (audio_from_video, video_timeline->get_video_file_fps());
338 				/* TODO ASK user which channel:  0 .. ltcr->channels() - 1 */
339 
340 				ltc_seq = ltcr.read_ltc (/*channel*/ 0, /*max LTC samples to decode*/ 15);
341 
342 				/* TODO seek near end of file, and read LTC until end.
343 				 * if it fails to find any LTC samples, scan complete file
344 				 *
345 				 * calculate drift of LTC compared to video-duration,
346 				 * ask user for reference (timecode from start/mid/end)
347 				 */
348 			} catch (...) {
349 				// LTCFileReader will have written error messages
350 			}
351 
352 			::g_unlink(audio_from_video.c_str());
353 
354 			if (ltc_seq.size() == 0) {
355 				PBD::error << _("No LTC detected, video will not be aligned.") << endmsg;
356 			} else {
357 				/* the very first TC in the file is somteimes not aligned properly */
358 				int i = ltc_seq.size() -1;
359 				ARDOUR::sampleoffset_t video_start_offset =
360 					_session->nominal_sample_rate() * (ltc_seq[i].timecode_sec - ltc_seq[i].framepos_sec);
361 				PBD::info << string_compose (_("Align video-start to %1 [samples]"), video_start_offset) << endmsg;
362 				video_timeline->set_offset(video_start_offset);
363 			}
364 		}
365 
366 		_session->maybe_update_session_range(
367 			std::max(video_timeline->get_offset(), (ARDOUR::sampleoffset_t) 0),
368 			std::max(video_timeline->get_offset() + video_timeline->get_duration(), (ARDOUR::sampleoffset_t) 0));
369 
370 
371 		if (add_video_dialog->launch_xjadeo() && local_file) {
372 			editor->set_xjadeo_sensitive(true);
373 			editor->toggle_xjadeo_proc(1);
374 		} else {
375 			editor->toggle_xjadeo_proc(0);
376 		}
377 		editor->toggle_ruler_video(true);
378 	}
379 }
380 
381 void
remove_video()382 ARDOUR_UI::remove_video ()
383 {
384 	video_timeline->close_session();
385 	editor->toggle_ruler_video(false);
386 
387 	/* reset state */
388 	video_timeline->set_offset_locked(false);
389 	video_timeline->set_offset(0);
390 
391 	/* delete session state */
392 	XMLNode* node = new XMLNode(X_("Videotimeline"));
393 	_session->add_extra_xml(*node);
394 	node = new XMLNode(X_("Videomonitor"));
395 	_session->add_extra_xml(*node);
396 	node = new XMLNode(X_("Videoexport"));
397 	_session->add_extra_xml(*node);
398 	stop_video_server();
399 }
400 
401 void
flush_videotimeline_cache(bool localcacheonly)402 ARDOUR_UI::flush_videotimeline_cache (bool localcacheonly)
403 {
404 	if (localcacheonly) {
405 		video_timeline->vmon_update();
406 	} else {
407 		video_timeline->flush_cache();
408 	}
409 	editor->queue_visual_videotimeline_update();
410 }
411 
412 void
export_video(bool range)413 ARDOUR_UI::export_video (bool range)
414 {
415 	if (ARDOUR::Config->get_show_video_export_info()) {
416 		ExportVideoInfobox infobox (_session);
417 		Gtk::ResponseType rv = (Gtk::ResponseType) infobox.run();
418 		if (infobox.show_again()) {
419 			ARDOUR::Config->set_show_video_export_info(false);
420 		}
421 		switch (rv) {
422 			case RESPONSE_YES:
423 				PBD::open_uri (ARDOUR::Config->get_reference_manual_url() + "/video-timeline/operations/#export");
424 				break;
425 			default:
426 				break;
427 		}
428 	}
429 	export_video_dialog->set_session (_session);
430 	export_video_dialog->apply_state(editor->get_selection().time, range);
431 	export_video_dialog->run ();
432 	export_video_dialog->hide ();
433 }
434