2 Copyright (C) 2008 Paul Davis
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.
24 #include <glibmm/fileutils.h>
26 #include "pbd/enumwriter.h"
27 #include "pbd/xml++.h"
28 #include "pbd/convert.h"
30 #include "ardour/export_profile_manager.h"
31 #include "ardour/export_format_specification.h"
32 #include "ardour/export_timespan.h"
33 #include "ardour/export_channel_configuration.h"
34 #include "ardour/export_filename.h"
35 #include "ardour/export_preset.h"
36 #include "ardour/export_handler.h"
37 #include "ardour/export_failed.h"
38 #include "ardour/filename_extensions.h"
39 #include "ardour/route.h"
40 #include "ardour/session.h"
51 ExportProfileManager::ExportProfileManager (Session
& s
) :
52 handler (s
.get_export_handler()),
55 session_range (new Location ()),
56 ranges (new LocationList ()),
57 single_range_mode (false),
59 format_list (new FormatList ())
62 /* Initialize path variables */
64 export_config_dir
= user_config_directory();
65 export_config_dir
/= "export";
66 search_path
+= export_config_dir
;
68 search_path
+= ardour_search_path().add_subdirectory_to_paths("export");
69 search_path
+= system_config_search_path().add_subdirectory_to_paths("export");;
71 /* create export config directory if necessary */
73 if (!sys::exists (export_config_dir
)) {
74 sys::create_directory (export_config_dir
);
80 /* Initialize all lists with an empty config */
83 init_timespans (dummy
);
84 init_channel_configs (dummy
);
86 init_filenames (dummy
);
89 ExportProfileManager::~ExportProfileManager ()
91 if (single_range_mode
) { return; }
93 XMLNode
* instant_xml (new XMLNode ("ExportProfile"));
94 serialize_profile (*instant_xml
);
95 session
.add_instant_xml (*instant_xml
, false);
99 ExportProfileManager::load_profile ()
101 XMLNode
* instant_node
= session
.instant_xml ("ExportProfile");
103 set_state (*instant_node
);
105 XMLNode
empty_node ("ExportProfile");
106 set_state (empty_node
);
111 ExportProfileManager::prepare_for_export ()
113 ChannelConfigPtr channel_config
= channel_configs
.front()->config
;
114 TimespanListPtr ts_list
= timespans
.front()->timespans
;
116 FormatStateList::const_iterator format_it
;
117 FilenameStateList::const_iterator filename_it
;
119 for (TimespanList::iterator ts_it
= ts_list
->begin(); ts_it
!= ts_list
->end(); ++ts_it
) {
120 for (format_it
= formats
.begin(), filename_it
= filenames
.begin();
121 format_it
!= formats
.end() && filename_it
!= filenames
.end();
122 ++format_it
, ++filename_it
) {
124 // filename->include_timespan = (ts_list->size() > 1); Disabled for now...
125 handler
->add_export_config (*ts_it
, channel_config
, (*format_it
)->format
, (*filename_it
)->filename
);
131 ExportProfileManager::load_preset (PresetPtr preset
)
135 current_preset
= preset
;
136 if (!preset
) { return false; }
138 XMLNode
const * state
;
139 if ((state
= preset
->get_local_state())) {
140 set_local_state (*state
);
141 } else { ok
= false; }
143 if ((state
= preset
->get_global_state())) {
144 if (!set_global_state (*state
)) {
147 } else { ok
= false; }
153 ExportProfileManager::load_presets ()
155 vector
<sys::path
> found
= find_file (string_compose (X_("*%1"),export_preset_suffix
));
157 for (vector
<sys::path
>::iterator it
= found
.begin(); it
!= found
.end(); ++it
) {
158 load_preset_from_disk (*it
);
162 ExportProfileManager::PresetPtr
163 ExportProfileManager::save_preset (string
const & name
)
165 string filename
= export_config_dir
.to_string() + "/" + name
+ export_preset_suffix
;
167 if (!current_preset
) {
168 current_preset
.reset (new ExportPreset (filename
, session
));
169 preset_list
.push_back (current_preset
);
172 XMLNode
* global_preset
= new XMLNode ("ExportPreset");
173 XMLNode
* local_preset
= new XMLNode ("ExportPreset");
175 serialize_global_profile (*global_preset
);
176 serialize_local_profile (*local_preset
);
178 current_preset
->set_name (name
);
179 current_preset
->set_global_state (*global_preset
);
180 current_preset
->set_local_state (*local_preset
);
182 current_preset
->save (filename
);
184 return current_preset
;
188 ExportProfileManager::remove_preset ()
190 if (!current_preset
) { return; }
192 for (PresetList::iterator it
= preset_list
.begin(); it
!= preset_list
.end(); ++it
) {
193 if (*it
== current_preset
) {
194 preset_list
.erase (it
);
199 FileMap::iterator it
= preset_file_map
.find (current_preset
->id());
200 if (it
!= preset_file_map
.end()) {
201 sys::remove (it
->second
);
202 preset_file_map
.erase (it
);
205 current_preset
->remove_local();
206 current_preset
.reset();
210 ExportProfileManager::load_preset_from_disk (PBD::sys::path
const & path
)
212 PresetPtr
preset (new ExportPreset (path
.to_string(), session
));
214 /* Handle id to filename mapping and don't add duplicates to list */
216 FilePair
pair (preset
->id(), path
);
217 if (preset_file_map
.insert (pair
).second
) {
218 preset_list
.push_back (preset
);
223 ExportProfileManager::set_state (XMLNode
const & root
)
225 return set_global_state (root
) & set_local_state (root
);
229 ExportProfileManager::set_global_state (XMLNode
const & root
)
231 return init_filenames (root
.children ("ExportFilename")) &
232 init_formats (root
.children ("ExportFormat"));
236 ExportProfileManager::set_local_state (XMLNode
const & root
)
238 return init_timespans (root
.children ("ExportTimespan")) &
239 init_channel_configs (root
.children ("ExportChannelConfiguration"));
243 ExportProfileManager::serialize_profile (XMLNode
& root
)
245 serialize_local_profile (root
);
246 serialize_global_profile (root
);
250 ExportProfileManager::serialize_global_profile (XMLNode
& root
)
252 for (FormatStateList::iterator it
= formats
.begin(); it
!= formats
.end(); ++it
) {
253 root
.add_child_nocopy (serialize_format (*it
));
256 for (FilenameStateList::iterator it
= filenames
.begin(); it
!= filenames
.end(); ++it
) {
257 root
.add_child_nocopy ((*it
)->filename
->get_state());
262 ExportProfileManager::serialize_local_profile (XMLNode
& root
)
264 for (TimespanStateList::iterator it
= timespans
.begin(); it
!= timespans
.end(); ++it
) {
265 root
.add_child_nocopy (serialize_timespan (*it
));
268 for (ChannelConfigStateList::iterator it
= channel_configs
.begin(); it
!= channel_configs
.end(); ++it
) {
269 root
.add_child_nocopy ((*it
)->config
->get_state());
273 std::vector
<sys::path
>
274 ExportProfileManager::find_file (std::string
const & pattern
)
276 vector
<sys::path
> found
;
278 Glib::PatternSpec
pattern_spec (pattern
);
279 find_matching_files_in_search_path (search_path
, pattern_spec
, found
);
285 ExportProfileManager::set_selection_range (nframes_t start
, nframes_t end
)
289 selection_range
.reset (new Location());
290 selection_range
->set_name (_("Selection"));
291 selection_range
->set (start
, end
);
293 selection_range
.reset();
296 for (TimespanStateList::iterator it
= timespans
.begin(); it
!= timespans
.end(); ++it
) {
297 (*it
)->selection_range
= selection_range
;
302 ExportProfileManager::set_single_range (nframes_t start
, nframes_t end
, Glib::ustring name
)
304 single_range_mode
= true;
306 single_range
.reset (new Location());
307 single_range
->set_name (name
);
308 single_range
->set (start
, end
);
312 return single_range
->id().to_s();
316 ExportProfileManager::init_timespans (XMLNodeList nodes
)
322 for (XMLNodeList::const_iterator it
= nodes
.begin(); it
!= nodes
.end(); ++it
) {
323 TimespanStatePtr span
= deserialize_timespan (**it
);
325 timespans
.push_back (span
);
326 } else { ok
= false; }
329 if (timespans
.empty()) {
330 TimespanStatePtr
state (new TimespanState (session_range
, selection_range
, ranges
));
331 timespans
.push_back (state
);
333 // Add session as default selection
334 TimespanPtr timespan
= handler
->add_timespan();
335 timespan
->set_name (session_range
->name());
336 timespan
->set_range_id ("session");
337 timespan
->set_range (session_range
->start(), session_range
->end());
338 state
->timespans
->push_back (timespan
);
345 ExportProfileManager::TimespanStatePtr
346 ExportProfileManager::deserialize_timespan (XMLNode
& root
)
348 TimespanStatePtr
state (new TimespanState (session_range
, selection_range
, ranges
));
349 XMLProperty
const * prop
;
351 XMLNodeList spans
= root
.children ("Range");
352 for (XMLNodeList::iterator node_it
= spans
.begin(); node_it
!= spans
.end(); ++node_it
) {
354 prop
= (*node_it
)->property ("id");
355 if (!prop
) { continue; }
356 ustring id
= prop
->value();
358 for (LocationList::iterator it
= ranges
->begin(); it
!= ranges
->end(); ++it
) {
359 if ((!id
.compare ("session") && *it
== session_range
.get()) ||
360 (!id
.compare ("selection") && *it
== selection_range
.get()) ||
361 (!id
.compare ((*it
)->id().to_s()))) {
362 TimespanPtr timespan
= handler
->add_timespan();
363 timespan
->set_name ((*it
)->name());
364 timespan
->set_range_id (id
);
365 timespan
->set_range ((*it
)->start(), (*it
)->end());
366 state
->timespans
->push_back (timespan
);
371 if ((prop
= root
.property ("format"))) {
372 state
->time_format
= (TimeFormat
) string_2_enum (prop
->value(), TimeFormat
);
379 ExportProfileManager::serialize_timespan (TimespanStatePtr state
)
381 XMLNode
& root
= *(new XMLNode ("ExportTimespan"));
386 for (TimespanList::iterator it
= state
->timespans
->begin(); it
!= state
->timespans
->end(); ++it
) {
387 if ((span
= root
.add_child ("Range"))) {
388 span
->add_property ("id", (*it
)->range_id());
392 root
.add_property ("format", enum_2_string (state
->time_format
));
398 ExportProfileManager::update_ranges () {
401 if (single_range_mode
) {
402 ranges
->push_back (single_range
.get());
408 session_range
->set_name (_("Session"));
409 session_range
->set (session
.current_start_frame(), session
.current_end_frame());
410 ranges
->push_back (session_range
.get());
414 if (selection_range
) {
415 ranges
->push_back (selection_range
.get());
420 LocationList
const & list (session
.locations()->list());
421 for (LocationList::const_iterator it
= list
.begin(); it
!= list
.end(); ++it
) {
422 if ((*it
)->is_range_marker()) {
423 ranges
->push_back (*it
);
429 ExportProfileManager::init_channel_configs (XMLNodeList nodes
)
431 channel_configs
.clear();
434 ChannelConfigStatePtr
config (new ChannelConfigState (handler
->add_channel_config()));
435 channel_configs
.push_back (config
);
437 // Add master outs as default
438 IO
* master_out
= session
.master_out()->output().get();
439 if (!master_out
) { return false; }
441 for (uint32_t n
= 0; n
< master_out
->n_ports().n_audio(); ++n
) {
442 PortExportChannel
* channel
= new PortExportChannel ();
443 channel
->add_port (master_out
->audio (n
));
445 ExportChannelPtr
chan_ptr (channel
);
446 config
->config
->register_channel (chan_ptr
);
451 for (XMLNodeList::const_iterator it
= nodes
.begin(); it
!= nodes
.end(); ++it
) {
452 ChannelConfigStatePtr
config (new ChannelConfigState (handler
->add_channel_config()));
453 config
->config
->set_state (**it
);
454 channel_configs
.push_back (config
);
460 ExportProfileManager::FormatStatePtr
461 ExportProfileManager::duplicate_format_state (FormatStatePtr state
)
463 /* Note: The pointer in the new FormatState should point to the same format spec
464 as the original state's pointer. The spec itself should not be copied! */
466 FormatStatePtr
format (new FormatState (format_list
, state
->format
));
467 formats
.push_back (format
);
472 ExportProfileManager::remove_format_state (FormatStatePtr state
)
474 for (FormatStateList::iterator it
= formats
.begin(); it
!= formats
.end(); ++it
) {
483 ExportProfileManager::save_format_to_disk (FormatPtr format
)
485 // TODO filename character stripping
487 /* Get filename for file */
489 Glib::ustring new_name
= format
->name();
490 new_name
+= export_format_suffix
;
492 sys::path
new_path (export_config_dir
);
493 new_path
/= new_name
;
495 /* Check if format is on disk already */
496 FileMap::iterator it
;
497 if ((it
= format_file_map
.find (format
->id())) != format_file_map
.end()) {
499 /* Check if config is not in user config dir */
500 if (it
->second
.branch_path().to_string().compare (export_config_dir
.to_string())) {
504 XMLTree
tree (new_path
.to_string());
505 tree
.set_root (&format
->get_state());
510 /* Update file and rename if necessary */
512 XMLTree
tree (it
->second
.to_string());
513 tree
.set_root (&format
->get_state());
516 if (new_name
.compare (it
->second
.leaf())) {
517 sys::rename (it
->second
, new_path
);
521 it
->second
= new_path
;
526 XMLTree
tree (new_path
.to_string());
527 tree
.set_root (&format
->get_state());
531 FormatListChanged ();
536 ExportProfileManager::remove_format_profile (FormatPtr format
)
538 for (FormatList::iterator it
= format_list
->begin(); it
!= format_list
->end(); ++it
) {
540 format_list
->erase (it
);
545 FileMap::iterator it
= format_file_map
.find (format
->id());
546 if (it
!= format_file_map
.end()) {
547 sys::remove (it
->second
);
548 format_file_map
.erase (it
);
551 FormatListChanged ();
554 ExportProfileManager::FormatPtr
555 ExportProfileManager::get_new_format (FormatPtr original
)
559 format
.reset (new ExportFormatSpecification (*original
));
561 format
= handler
->add_format();
562 format
->set_name ("empty format");
565 sys::path path
= save_format_to_disk (format
);
566 FilePair
pair (format
->id(), path
);
567 format_file_map
.insert (pair
);
569 format_list
->push_back (format
);
570 FormatListChanged ();
576 ExportProfileManager::init_formats (XMLNodeList nodes
)
581 for (XMLNodeList::const_iterator it
= nodes
.begin(); it
!= nodes
.end(); ++it
) {
582 FormatStatePtr format
= deserialize_format (**it
);
584 formats
.push_back (format
);
585 } else { ok
= false; }
588 if (formats
.empty ()) {
589 FormatStatePtr
format (new FormatState (format_list
, FormatPtr ()));
590 formats
.push_back (format
);
597 ExportProfileManager::FormatStatePtr
598 ExportProfileManager::deserialize_format (XMLNode
& root
)
603 if ((prop
= root
.property ("id"))) {
607 for (FormatList::iterator it
= format_list
->begin(); it
!= format_list
->end(); ++it
) {
608 if ((*it
)->id() == id
) {
609 return FormatStatePtr (new FormatState (format_list
, *it
));
613 return FormatStatePtr ();
617 ExportProfileManager::serialize_format (FormatStatePtr state
)
619 XMLNode
* root
= new XMLNode ("ExportFormat");
621 string id
= state
->format
? state
->format
->id().to_s() : "";
622 root
->add_property ("id", id
);
628 ExportProfileManager::load_formats ()
630 vector
<sys::path
> found
= find_file (string_compose ("*%1", export_format_suffix
));
632 for (vector
<sys::path
>::iterator it
= found
.begin(); it
!= found
.end(); ++it
) {
633 load_format_from_disk (*it
);
638 ExportProfileManager::load_format_from_disk (PBD::sys::path
const & path
)
640 XMLTree
const tree (path
.to_string());
641 FormatPtr format
= handler
->add_format (*tree
.root());
643 /* Handle id to filename mapping and don't add duplicates to list */
645 FilePair
pair (format
->id(), path
);
646 if (format_file_map
.insert (pair
).second
) {
647 format_list
->push_back (format
);
650 FormatListChanged ();
653 ExportProfileManager::FilenameStatePtr
654 ExportProfileManager::duplicate_filename_state (FilenameStatePtr state
)
656 FilenameStatePtr
filename (new FilenameState (handler
->add_filename_copy (state
->filename
)));
657 filenames
.push_back (filename
);
662 ExportProfileManager::remove_filename_state (FilenameStatePtr state
)
664 for (FilenameStateList::iterator it
= filenames
.begin(); it
!= filenames
.end(); ++it
) {
666 filenames
.erase (it
);
673 ExportProfileManager::init_filenames (XMLNodeList nodes
)
677 for (XMLNodeList::const_iterator it
= nodes
.begin(); it
!= nodes
.end(); ++it
) {
678 FilenamePtr filename
= handler
->add_filename();
679 filename
->set_state (**it
);
680 filenames
.push_back (FilenameStatePtr (new FilenameState (filename
)));
683 if (filenames
.empty()) {
684 FilenameStatePtr
filename (new FilenameState (handler
->add_filename()));
685 filenames
.push_back (filename
);
692 boost::shared_ptr
<ExportProfileManager::Warnings
>
693 ExportProfileManager::get_warnings ()
695 boost::shared_ptr
<Warnings
> warnings (new Warnings ());
697 ChannelConfigStatePtr channel_config_state
= channel_configs
.front();
698 TimespanStatePtr timespan_state
= timespans
.front();
700 /*** Check "global" config ***/
702 TimespanListPtr timespans
= timespan_state
->timespans
;
703 ChannelConfigPtr channel_config
= channel_config_state
->config
;
705 /* Check Timespans are not empty */
707 if (timespans
->empty()) {
708 warnings
->errors
.push_back (_("No timespan has been selected!"));
711 /* Check channel config ports */
713 if (!channel_config
->all_channels_have_ports ()) {
714 warnings
->warnings
.push_back (_("Some channels are empty"));
717 /*** Check files ***/
719 FormatStateList::const_iterator format_it
;
720 FilenameStateList::const_iterator filename_it
;
721 for (format_it
= formats
.begin(), filename_it
= filenames
.begin();
722 format_it
!= formats
.end() && filename_it
!= filenames
.end();
723 ++format_it
, ++filename_it
) {
724 check_config (warnings
, timespan_state
, channel_config_state
, *format_it
, *filename_it
);
731 ExportProfileManager::check_config (boost::shared_ptr
<Warnings
> warnings
,
732 TimespanStatePtr timespan_state
,
733 ChannelConfigStatePtr channel_config_state
,
734 FormatStatePtr format_state
,
735 FilenameStatePtr filename_state
)
737 TimespanListPtr timespans
= timespan_state
->timespans
;
738 ChannelConfigPtr channel_config
= channel_config_state
->config
;
739 FormatPtr format
= format_state
->format
;
740 FilenamePtr filename
= filename_state
->filename
;
742 /* Check format and maximum channel count */
743 if (!format
|| !format
->type()) {
744 warnings
->errors
.push_back (_("No format selected!"));
745 } else if (!channel_config
->get_n_chans()) {
746 warnings
->errors
.push_back (_("All channels are empty!"));
747 } else if (!check_format (format
, channel_config
->get_n_chans())) {
748 warnings
->errors
.push_back (_("One or more of the selected formats is not compatible with this system!"));
749 } else if (format
->channel_limit() < channel_config
->get_n_chans()) {
750 warnings
->errors
.push_back
751 (string_compose (_("%1 supports only %2 channels, but you have %3 channels in your channel configuration"),
752 format
->format_name(),
753 format
->channel_limit(),
754 channel_config
->get_n_chans()));
757 if (!warnings
->errors
.empty()) { return; }
759 /* Check filenames */
761 // filename->include_timespan = (timespans->size() > 1); Disabled for now...
763 for (std::list
<TimespanPtr
>::iterator timespan_it
= timespans
->begin(); timespan_it
!= timespans
->end(); ++timespan_it
) {
764 filename
->set_timespan (*timespan_it
);
766 if (channel_config
->get_split()) {
767 filename
->include_channel
= true;
769 for (uint32_t chan
= 1; chan
<= channel_config
->get_n_chans(); ++chan
) {
770 filename
->set_channel (chan
);
772 Glib::ustring path
= filename
->get_path (format
);
774 if (sys::exists (sys::path (path
))) {
775 warnings
->conflicting_filenames
.push_back (path
);
780 Glib::ustring path
= filename
->get_path (format
);
782 if (sys::exists (sys::path (path
))) {
783 warnings
->conflicting_filenames
.push_back (path
);
790 ExportProfileManager::check_format (FormatPtr format
, uint32_t channels
)
792 switch (format
->type()) {
793 case ExportFormatBase::T_Sndfile
:
794 return check_sndfile_format (format
, channels
);
797 throw ExportFailed (X_("Invalid format given for ExportFileFactory::check!"));
802 ExportProfileManager::check_sndfile_format (FormatPtr format
, unsigned int channels
)
805 sf_info
.channels
= channels
;
806 sf_info
.samplerate
= format
->sample_rate ();
807 sf_info
.format
= format
->format_id () | format
->sample_format ();
809 return (sf_format_check (&sf_info
) == SF_TRUE
? true : false);
812 }; // namespace ARDOUR