* Region export dialog does not lose export settings (Do not serialize into instant...
[ardour.git] / libs / ardour / export_profile_manager.cc
1 /*
2     Copyright (C) 2008 Paul Davis
3     Author: Sakari Bergen
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
21 #include <ardour/export_profile_manager.h>
22
23 #include <cassert>
24 #include <stdexcept>
25
26 #include <glibmm/fileutils.h>
27
28 #include <pbd/enumwriter.h>
29 #include <pbd/xml++.h>
30 #include <pbd/convert.h>
31
32 #include <ardour/export_failed.h>
33 #include <ardour/export_file_io.h>
34 #include <ardour/export_format_specification.h>
35 #include <ardour/export_timespan.h>
36 #include <ardour/export_channel_configuration.h>
37 #include <ardour/export_filename.h>
38 #include <ardour/export_preset.h>
39 #include <ardour/export_handler.h>
40 #include <ardour/filename_extensions.h>
41 #include <ardour/session.h>
42
43 #include "i18n.h"
44
45 using namespace PBD;
46
47 namespace ARDOUR
48 {
49
50 ExportProfileManager::ExportProfileManager (Session & s) :
51   handler (s.get_export_handler()),
52   session (s),
53
54   session_range (new Location ()),
55   ranges (new LocationList ()),
56   single_range_mode (false),
57
58   format_list (new FormatList ())
59 {
60
61         /* Initialize path variables */
62
63         sys::path path;
64
65         export_config_dir = user_config_directory();
66         export_config_dir /= "export";
67         search_path += export_config_dir;
68         
69         path = ardour_search_path().to_string();
70         path /= "export";
71         search_path += path;
72         
73         path = system_config_search_path().to_string();
74         path /= "export";
75         search_path += path;
76         
77         /* create export config directory if necessary */
78
79         if (!sys::exists (export_config_dir)) {
80                 sys::create_directory (export_config_dir);
81         }
82         
83         load_presets ();
84         load_formats ();
85         
86         /* Initialize all lists with an empty config */
87         
88         XMLNodeList dummy;
89         init_timespans (dummy);
90         init_channel_configs (dummy);
91         init_formats (dummy);
92         init_filenames (dummy);
93 }
94
95 ExportProfileManager::~ExportProfileManager ()
96 {
97         if (single_range_mode) { return; }
98         
99         XMLNode * instant_xml (new XMLNode ("ExportProfile"));
100         serialize_profile (*instant_xml);
101         session.add_instant_xml (*instant_xml, false);
102 }
103
104 void
105 ExportProfileManager::load_profile ()
106 {
107         XMLNode * instant_node = session.instant_xml ("ExportProfile");
108         if (instant_node) {
109                 set_state (*instant_node);
110         } else {
111                 XMLNode empty_node ("ExportProfile");
112                 set_state (empty_node);
113         }
114 }
115
116 void
117 ExportProfileManager::prepare_for_export ()
118 {
119         ChannelConfigPtr channel_config = channel_configs.front()->config;
120         TimespanListPtr ts_list = timespans.front()->timespans;
121         
122         FormatStateList::const_iterator format_it;
123         FilenameStateList::const_iterator filename_it;
124         
125         for (TimespanList::iterator ts_it = ts_list->begin(); ts_it != ts_list->end(); ++ts_it) {
126                 for (format_it = formats.begin(), filename_it = filenames.begin();
127                      format_it != formats.end() && filename_it != filenames.end();
128                      ++format_it, ++filename_it) {
129                 
130 //                      filename->include_timespan = (ts_list->size() > 1); Disabled for now...
131                         handler->add_export_config (*ts_it, channel_config, (*format_it)->format, (*filename_it)->filename);
132                 }
133         }
134 }
135
136 bool
137 ExportProfileManager::load_preset (PresetPtr preset)
138 {
139         bool ok = true;
140
141         current_preset = preset;
142         if (!preset) { return false; }
143
144         XMLNode const * state;
145         if ((state = preset->get_local_state())) {
146                 set_local_state (*state);
147         } else { ok = false; }
148         
149         if ((state = preset->get_global_state())) {
150                 if (!set_global_state (*state)) {
151                         ok = false;
152                 }
153         } else { ok = false; }
154         
155         return ok;
156 }
157
158 void
159 ExportProfileManager::load_presets ()
160 {
161         vector<sys::path> found = find_file (string_compose (X_("*%1"),export_preset_suffix));
162
163         for (vector<sys::path>::iterator it = found.begin(); it != found.end(); ++it) {
164                 load_preset_from_disk (*it);
165         }
166 }
167
168 ExportProfileManager::PresetPtr
169 ExportProfileManager::save_preset (string const & name)
170 {
171         if (!current_preset) {
172                 string filename = export_config_dir.to_string() + "/" + name + export_preset_suffix;
173                 current_preset.reset (new ExportPreset (filename, session));
174                 preset_list.push_back (current_preset);
175         }
176         
177         XMLNode * global_preset = new XMLNode ("ExportPreset");
178         XMLNode * local_preset = new XMLNode ("ExportPreset");
179
180         serialize_global_profile (*global_preset);
181         serialize_local_profile (*local_preset);
182
183         current_preset->set_name (name);
184         current_preset->set_global_state (*global_preset);
185         current_preset->set_local_state (*local_preset);
186         
187         current_preset->save();
188         
189         return current_preset;
190 }
191
192 void
193 ExportProfileManager::remove_preset ()
194 {
195         if (!current_preset) { return; }
196
197         for (PresetList::iterator it = preset_list.begin(); it != preset_list.end(); ++it) {
198                 if (*it == current_preset) {
199                         preset_list.erase (it);
200                         break;
201                 }
202         }
203
204         FileMap::iterator it = preset_file_map.find (current_preset->id());
205         if (it != preset_file_map.end()) {
206                 sys::remove (it->second);
207                 preset_file_map.erase (it);
208         }
209         
210         current_preset->remove_local();
211         current_preset.reset();
212 }
213
214 void
215 ExportProfileManager::load_preset_from_disk (PBD::sys::path const & path)
216 {
217         PresetPtr preset (new ExportPreset (path.to_string(), session));
218         preset_list.push_back (preset);
219         
220         /* Handle id to filename mapping */
221         
222         FilePair pair (preset->id(), path);
223         preset_file_map.insert (pair);
224 }
225
226 bool
227 ExportProfileManager::set_state (XMLNode const & root)
228 {
229         return set_global_state (root) && set_local_state (root);
230 }
231
232 bool
233 ExportProfileManager::set_global_state (XMLNode const & root)
234 {
235         return init_filenames (root.children ("ExportFilename")) &&
236                init_formats (root.children ("ExportFormat"));
237 }
238
239 bool
240 ExportProfileManager::set_local_state (XMLNode const & root)
241 {
242         return init_timespans (root.children ("ExportTimespan")) &&
243                init_channel_configs (root.children ("ExportChannelConfiguration"));
244 }
245
246 void
247 ExportProfileManager::serialize_profile (XMLNode & root)
248 {
249         serialize_local_profile (root);
250         serialize_global_profile (root);
251 }
252
253 void
254 ExportProfileManager::serialize_global_profile (XMLNode & root)
255 {
256         for (FormatStateList::iterator it = formats.begin(); it != formats.end(); ++it) {
257                 root.add_child_nocopy (serialize_format (*it));
258         }
259
260         for (FilenameStateList::iterator it = filenames.begin(); it != filenames.end(); ++it) {
261                 root.add_child_nocopy ((*it)->filename->get_state());
262         }
263 }
264
265 void
266 ExportProfileManager::serialize_local_profile (XMLNode & root)
267 {
268         for (TimespanStateList::iterator it = timespans.begin(); it != timespans.end(); ++it) {
269                 root.add_child_nocopy (serialize_timespan (*it));
270         }
271         
272         for (ChannelConfigStateList::iterator it = channel_configs.begin(); it != channel_configs.end(); ++it) {
273                 root.add_child_nocopy ((*it)->config->get_state());
274         }
275 }
276
277 std::vector<sys::path>
278 ExportProfileManager::find_file (std::string const & pattern)
279 {
280         vector<sys::path> found;
281
282         Glib::PatternSpec pattern_spec (pattern);
283         find_matching_files_in_search_path (search_path, pattern_spec, found);
284
285         return found;
286 }
287
288 void
289 ExportProfileManager::set_selection_range (nframes_t start, nframes_t end)
290 {
291
292         if (start || end) {
293                 selection_range.reset (new Location());
294                 selection_range->set_name (_("Selection"));
295                 selection_range->set (start, end);
296         } else {
297                 selection_range.reset();
298         }
299         
300         for (TimespanStateList::iterator it = timespans.begin(); it != timespans.end(); ++it) {
301                 (*it)->selection_range = selection_range;
302         }
303 }
304
305 std::string
306 ExportProfileManager::set_single_range (nframes_t start, nframes_t end, Glib::ustring name)
307 {
308         single_range_mode = true;
309         
310         single_range.reset (new Location());
311         single_range->set_name (name);
312         single_range->set (start, end);
313         
314         update_ranges ();
315         
316         return single_range->id().to_s();
317 }
318
319 bool
320 ExportProfileManager::init_timespans (XMLNodeList nodes)
321 {
322         timespans.clear ();
323         update_ranges ();
324         
325         bool ok = true;
326         for (XMLNodeList::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
327                 TimespanStatePtr span = deserialize_timespan (**it);
328                 if (span) {
329                         timespans.push_back (span);
330                 } else { ok = false; }
331         }
332         
333         if (timespans.empty()) {
334                 TimespanStatePtr timespan (new TimespanState (session_range, selection_range, ranges));
335                 timespans.push_back (timespan);
336                 return false;
337         }
338         
339         return ok;
340 }
341
342 ExportProfileManager::TimespanStatePtr
343 ExportProfileManager::deserialize_timespan (XMLNode & root)
344 {
345         TimespanStatePtr state (new TimespanState (session_range, selection_range, ranges));
346         XMLProperty const * prop;
347         
348         XMLNodeList spans = root.children ("Range");
349         for (XMLNodeList::iterator node_it = spans.begin(); node_it != spans.end(); ++node_it) {
350                 
351                 prop = (*node_it)->property ("id");
352                 if (!prop) { continue; }
353                 ustring id = prop->value();
354         
355                 for (LocationList::iterator it = ranges->begin(); it != ranges->end(); ++it) {
356                         if ((!id.compare ("session") && *it == session_range.get()) ||
357                             (!id.compare ("selection") && *it == selection_range.get()) ||
358                             (!id.compare ((*it)->id().to_s()))) {
359                                 TimespanPtr timespan = handler->add_timespan();
360                                 timespan->set_name ((*it)->name());
361                                 timespan->set_range_id (id);
362                                 timespan->set_range ((*it)->start(), (*it)->end());
363                                 state->timespans->push_back (timespan);
364                         }
365                 }
366         }
367         
368         if ((prop = root.property ("format"))) {
369                 state->time_format = (TimeFormat) string_2_enum (prop->value(), TimeFormat);
370         }
371         
372         return state;
373 }
374
375 XMLNode &
376 ExportProfileManager::serialize_timespan (TimespanStatePtr state)
377 {
378         XMLNode & root = *(new XMLNode ("ExportTimespan"));
379         XMLNode * span;
380         
381         update_ranges ();
382         
383         for (TimespanList::iterator it = state->timespans->begin(); it != state->timespans->end(); ++it) {
384                 if ((span = root.add_child ("Range"))) {
385                         span->add_property ("id", (*it)->range_id());
386                 }
387         }
388         
389         root.add_property ("format", enum_2_string (state->time_format));
390         
391         return root;
392 }
393
394 void
395 ExportProfileManager::update_ranges () {
396         ranges->clear();
397         
398         if (single_range_mode) {
399                 ranges->push_back (single_range.get());
400                 return;
401         }
402
403         /* Session */
404
405         session_range->set_name (_("Session"));
406         session_range->set (session.current_start_frame(), session.current_end_frame());
407         ranges->push_back (session_range.get());
408         
409         /* Selection */
410         
411         if (selection_range) {
412                 ranges->push_back (selection_range.get());
413         }
414         
415         /* ranges */
416         
417         LocationList const & list (session.locations()->list());
418         for (LocationList::const_iterator it = list.begin(); it != list.end(); ++it) {
419                 if ((*it)->is_range_marker()) {
420                         ranges->push_back (*it);
421                 }
422         }
423 }
424
425 bool
426 ExportProfileManager::init_channel_configs (XMLNodeList nodes)
427 {
428         channel_configs.clear();
429
430         if (nodes.empty()) {    
431                 ChannelConfigStatePtr config (new ChannelConfigState (handler->add_channel_config()));
432                 channel_configs.push_back (config);
433                 return false;
434         }
435         
436         for (XMLNodeList::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
437                 ChannelConfigStatePtr config (new ChannelConfigState (handler->add_channel_config()));
438                 config->config->set_state (**it);
439                 channel_configs.push_back (config);
440         }
441         
442         return true;
443 }
444
445 ExportProfileManager::FormatStatePtr
446 ExportProfileManager::duplicate_format_state (FormatStatePtr state)
447 {
448         /* Note: The pointer in the new FormatState should point to the same format spec
449                  as the original state's pointer. The spec itself should not be copied!   */
450
451         FormatStatePtr format (new FormatState (format_list, state->format));
452         formats.push_back (format);
453         return format;
454 }
455
456 void
457 ExportProfileManager::remove_format_state (FormatStatePtr state)
458 {
459         for (FormatStateList::iterator it = formats.begin(); it != formats.end(); ++it) {
460                 if (*it == state) {
461                         formats.erase (it);
462                         return;
463                 }
464         }
465 }
466
467 sys::path
468 ExportProfileManager::save_format_to_disk (FormatPtr format)
469 {
470         // TODO filename character stripping
471
472         /* Get filename for file */
473
474         Glib::ustring new_name = format->name();
475         new_name += export_format_suffix;
476         
477         sys::path new_path (export_config_dir);
478         new_path /= new_name;
479
480         /* Check if format is on disk already */
481         FileMap::iterator it;
482         if ((it = format_file_map.find (format->id())) != format_file_map.end()) {
483                 /* Update data */
484                 {
485                         XMLTree tree (it->second.to_string());
486                         tree.set_root (&format->get_state());
487                         tree.write();
488                 }
489                 
490                 /* Rename if necessary */
491                 
492                 if (new_name.compare (it->second.leaf())) {
493                         sys::rename (it->second, new_path);
494                 }
495                 
496         } else {
497                 /* Write new file */
498                 
499                 XMLTree tree (new_path.to_string());
500                 tree.set_root (&format->get_state());
501                 tree.write();
502         }
503         
504         FormatListChanged ();
505         return new_path;
506 }
507
508 void
509 ExportProfileManager::remove_format_profile (FormatPtr format)
510 {
511         for (FormatList::iterator it = format_list->begin(); it != format_list->end(); ++it) {
512                 if (*it == format) {
513                         format_list->erase (it);
514                         break;
515                 }
516         }
517
518         FileMap::iterator it = format_file_map.find (format->id());
519         if (it != format_file_map.end()) {
520                 sys::remove (it->second);
521                 format_file_map.erase (it);
522         }
523         
524         FormatListChanged ();
525 }
526
527 ExportProfileManager::FormatPtr
528 ExportProfileManager::get_new_format (FormatPtr original)
529 {       
530         FormatPtr format;
531         if (original) {
532                 format.reset (new ExportFormatSpecification (*original));
533         } else {
534                 format = handler->add_format();
535                 format->set_name ("empty format");
536         }
537         
538         sys::path path = save_format_to_disk (format);
539         FilePair pair (format->id(), path);
540         format_file_map.insert (pair);
541         
542         format_list->push_back (format);
543         FormatListChanged ();
544         
545         return format;
546 }
547
548 bool
549 ExportProfileManager::init_formats (XMLNodeList nodes)
550 {
551         formats.clear();
552
553         bool ok = true;
554         for (XMLNodeList::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
555                 FormatStatePtr format = deserialize_format (**it);
556                 if (format) {
557                         formats.push_back (format);
558                 } else { ok = false; }
559         }
560         
561         if (formats.empty ()) {
562                 FormatStatePtr format (new FormatState (format_list, FormatPtr ()));
563                 formats.push_back (format);
564                 return false;
565         }
566         
567         return ok;
568 }
569
570 ExportProfileManager::FormatStatePtr
571 ExportProfileManager::deserialize_format (XMLNode & root)
572 {
573         XMLProperty * prop;
574         UUID id;
575         
576         if ((prop = root.property ("id"))) {
577                 id = prop->value();
578         }
579         
580         for (FormatList::iterator it = format_list->begin(); it != format_list->end(); ++it) {
581                 if ((*it)->id() == id) {
582                         return FormatStatePtr (new FormatState (format_list, *it));
583                 }
584         }
585
586         return FormatStatePtr ();
587 }
588
589 XMLNode &
590 ExportProfileManager::serialize_format (FormatStatePtr state)
591 {
592         XMLNode * root = new XMLNode ("ExportFormat");
593         
594         string id = state->format ? state->format->id().to_s() : "";
595         root->add_property ("id", id);
596         
597         return *root;
598 }
599
600 void
601 ExportProfileManager::load_formats ()
602 {
603         vector<sys::path> found = find_file (string_compose ("*%1", export_format_suffix));
604
605         for (vector<sys::path>::iterator it = found.begin(); it != found.end(); ++it) {
606                 load_format_from_disk (*it);
607         }
608 }
609
610 void
611 ExportProfileManager::load_format_from_disk (PBD::sys::path const & path)
612 {
613         XMLTree const tree (path.to_string());
614         FormatPtr format = handler->add_format (*tree.root());
615         format_list->push_back (format);
616         
617         /* Handle id to filename mapping */
618         
619         FilePair pair (format->id(), path);
620         format_file_map.insert (pair);
621         
622         FormatListChanged ();
623 }
624
625 ExportProfileManager::FilenameStatePtr
626 ExportProfileManager::duplicate_filename_state (FilenameStatePtr state)
627 {
628         FilenameStatePtr filename (new FilenameState (handler->add_filename_copy (state->filename)));
629         filenames.push_back (filename);
630         return filename;
631 }
632
633 void
634 ExportProfileManager::remove_filename_state (FilenameStatePtr state)
635 {
636         for (FilenameStateList::iterator it = filenames.begin(); it != filenames.end(); ++it) {
637                 if (*it == state) {
638                         filenames.erase (it);
639                         return;
640                 }
641         }
642 }
643
644 bool
645 ExportProfileManager::init_filenames (XMLNodeList nodes)
646 {
647         filenames.clear ();
648
649         for (XMLNodeList::const_iterator it = nodes.begin(); it != nodes.end(); ++it) {
650                 FilenamePtr filename = handler->add_filename();
651                 filename->set_state (**it);
652                 filenames.push_back (FilenameStatePtr (new FilenameState (filename)));
653         }
654         
655         if (filenames.empty()) {
656                 FilenameStatePtr filename (new FilenameState (handler->add_filename()));
657                 filenames.push_back (filename);
658                 return false;
659         }
660         
661         return true;
662 }
663
664 boost::shared_ptr<ExportProfileManager::Warnings>
665 ExportProfileManager::get_warnings ()
666 {
667         boost::shared_ptr<Warnings> warnings (new Warnings ());
668
669         ChannelConfigStatePtr channel_config_state = channel_configs.front();
670         TimespanStatePtr timespan_state = timespans.front();
671         
672         /*** Check "global" config ***/
673         
674         TimespanListPtr timespans = timespan_state->timespans;
675         ChannelConfigPtr channel_config = channel_config_state->config;
676
677         /* Check Timespans are not empty */
678         
679         if (timespans->empty()) {
680                 warnings->errors.push_back (_("No timespan has been selected!"));
681         }
682
683         /* Check channel config ports */
684         
685         if (!channel_config->all_channels_have_ports ()) {
686                 warnings->warnings.push_back (_("Some channels are empty"));
687         }
688         
689         /*** Check files ***/
690         
691         FormatStateList::const_iterator format_it;
692         FilenameStateList::const_iterator filename_it;
693         for (format_it = formats.begin(), filename_it = filenames.begin();
694              format_it != formats.end() && filename_it != filenames.end();
695              ++format_it, ++filename_it) {
696                         check_config (warnings, timespan_state, channel_config_state, *format_it, *filename_it);
697         }
698         
699         return warnings;
700 }
701
702 void
703 ExportProfileManager::check_config (boost::shared_ptr<Warnings> warnings,
704                                     TimespanStatePtr timespan_state,
705                                     ChannelConfigStatePtr channel_config_state,
706                                     FormatStatePtr format_state,
707                                     FilenameStatePtr filename_state)
708 {
709         TimespanListPtr timespans = timespan_state->timespans;
710         ChannelConfigPtr channel_config = channel_config_state->config;
711         FormatPtr format = format_state->format;
712         FilenamePtr filename = filename_state->filename;
713
714         /* Check format and maximum channel count */
715         if (!format || !format->type()) {
716                 warnings->errors.push_back (_("No format selected!"));
717         } else if (!ExportFileFactory::check (format, channel_config->get_n_chans())) {
718                 warnings->errors.push_back (_("One or more of the selected formats is not compatible with this system!"));
719         } else if (format->channel_limit() < channel_config->get_n_chans()) {
720                 warnings->errors.push_back
721                   (string_compose (_("%1 supports only %2 channels, but you have %3 channels in your channel configuration"),
722                                      format->format_name(),
723                                      format->channel_limit(),
724                                      channel_config->get_n_chans()));
725         }
726         
727         if (!warnings->errors.empty()) { return; }
728         
729         /* Check filenames */
730         
731 //      filename->include_timespan = (timespans->size() > 1); Disabled for now...
732         
733         for (std::list<TimespanPtr>::iterator timespan_it = timespans->begin(); timespan_it != timespans->end(); ++timespan_it) {
734                 filename->set_timespan (*timespan_it);
735         
736                 if (channel_config->get_split()) {
737                         filename->include_channel = true;
738                         
739                         for (uint32_t chan = 1; chan <= channel_config->get_n_chans(); ++chan) {
740                                 filename->set_channel (chan);
741                                 
742                                 Glib::ustring path = filename->get_path (format);
743                                 
744                                 if (sys::exists (sys::path (path))) {
745                                         warnings->conflicting_filenames.push_back (path);
746                                 }
747                         }
748                         
749                 } else {
750                         Glib::ustring path = filename->get_path (format);
751                         
752                         if (sys::exists (sys::path (path))) {
753                                 warnings->conflicting_filenames.push_back (path);
754                         }
755                 }
756         }
757 }
758
759 }; // namespace ARDOUR