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/session.h"
50 ExportProfileManager::ExportProfileManager (Session
& s
) :
51 handler (s
.get_export_handler()),
54 session_range (new Location ()),
55 ranges (new LocationList ()),
56 single_range_mode (false),
58 format_list (new FormatList ())
61 /* Initialize path variables */
63 export_config_dir
= user_config_directory();
64 export_config_dir
/= "export";
65 search_path
+= export_config_dir
;
67 search_path
+= ardour_search_path().add_subdirectory_to_paths("export");
68 search_path
+= system_config_search_path().add_subdirectory_to_paths("export");;
70 /* create export config directory if necessary */
72 if (!sys::exists (export_config_dir
)) {
73 sys::create_directory (export_config_dir
);
79 /* Initialize all lists with an empty config */
82 init_timespans (dummy
);
83 init_channel_configs (dummy
);
85 init_filenames (dummy
);
88 ExportProfileManager::~ExportProfileManager ()
90 if (single_range_mode
) { return; }
92 XMLNode
* instant_xml (new XMLNode ("ExportProfile"));
93 serialize_profile (*instant_xml
);
94 session
.add_instant_xml (*instant_xml
, false);
98 ExportProfileManager::load_profile ()
100 XMLNode
* instant_node
= session
.instant_xml ("ExportProfile");
102 set_state (*instant_node
);
104 XMLNode
empty_node ("ExportProfile");
105 set_state (empty_node
);
110 ExportProfileManager::prepare_for_export ()
112 ChannelConfigPtr channel_config
= channel_configs
.front()->config
;
113 TimespanListPtr ts_list
= timespans
.front()->timespans
;
115 FormatStateList::const_iterator format_it
;
116 FilenameStateList::const_iterator filename_it
;
118 for (TimespanList::iterator ts_it
= ts_list
->begin(); ts_it
!= ts_list
->end(); ++ts_it
) {
119 for (format_it
= formats
.begin(), filename_it
= filenames
.begin();
120 format_it
!= formats
.end() && filename_it
!= filenames
.end();
121 ++format_it
, ++filename_it
) {
123 // filename->include_timespan = (ts_list->size() > 1); Disabled for now...
124 handler
->add_export_config (*ts_it
, channel_config
, (*format_it
)->format
, (*filename_it
)->filename
);
130 ExportProfileManager::load_preset (PresetPtr preset
)
134 current_preset
= preset
;
135 if (!preset
) { return false; }
137 XMLNode
const * state
;
138 if ((state
= preset
->get_local_state())) {
139 set_local_state (*state
);
140 } else { ok
= false; }
142 if ((state
= preset
->get_global_state())) {
143 if (!set_global_state (*state
)) {
146 } else { ok
= false; }
152 ExportProfileManager::load_presets ()
154 vector
<sys::path
> found
= find_file (string_compose (X_("*%1"),export_preset_suffix
));
156 for (vector
<sys::path
>::iterator it
= found
.begin(); it
!= found
.end(); ++it
) {
157 load_preset_from_disk (*it
);
161 ExportProfileManager::PresetPtr
162 ExportProfileManager::save_preset (string
const & name
)
164 string filename
= export_config_dir
.to_string() + "/" + name
+ export_preset_suffix
;
166 if (!current_preset
) {
167 current_preset
.reset (new ExportPreset (filename
, session
));
168 preset_list
.push_back (current_preset
);
171 XMLNode
* global_preset
= new XMLNode ("ExportPreset");
172 XMLNode
* local_preset
= new XMLNode ("ExportPreset");
174 serialize_global_profile (*global_preset
);
175 serialize_local_profile (*local_preset
);
177 current_preset
->set_name (name
);
178 current_preset
->set_global_state (*global_preset
);
179 current_preset
->set_local_state (*local_preset
);
181 current_preset
->save (filename
);
183 return current_preset
;
187 ExportProfileManager::remove_preset ()
189 if (!current_preset
) { return; }
191 for (PresetList::iterator it
= preset_list
.begin(); it
!= preset_list
.end(); ++it
) {
192 if (*it
== current_preset
) {
193 preset_list
.erase (it
);
198 FileMap::iterator it
= preset_file_map
.find (current_preset
->id());
199 if (it
!= preset_file_map
.end()) {
200 sys::remove (it
->second
);
201 preset_file_map
.erase (it
);
204 current_preset
->remove_local();
205 current_preset
.reset();
209 ExportProfileManager::load_preset_from_disk (PBD::sys::path
const & path
)
211 PresetPtr
preset (new ExportPreset (path
.to_string(), session
));
213 /* Handle id to filename mapping and don't add duplicates to list */
215 FilePair
pair (preset
->id(), path
);
216 if (preset_file_map
.insert (pair
).second
) {
217 preset_list
.push_back (preset
);
222 ExportProfileManager::set_state (XMLNode
const & root
)
224 return set_global_state (root
) && set_local_state (root
);
228 ExportProfileManager::set_global_state (XMLNode
const & root
)
230 return init_filenames (root
.children ("ExportFilename")) &&
231 init_formats (root
.children ("ExportFormat"));
235 ExportProfileManager::set_local_state (XMLNode
const & root
)
237 return init_timespans (root
.children ("ExportTimespan")) &&
238 init_channel_configs (root
.children ("ExportChannelConfiguration"));
242 ExportProfileManager::serialize_profile (XMLNode
& root
)
244 serialize_local_profile (root
);
245 serialize_global_profile (root
);
249 ExportProfileManager::serialize_global_profile (XMLNode
& root
)
251 for (FormatStateList::iterator it
= formats
.begin(); it
!= formats
.end(); ++it
) {
252 root
.add_child_nocopy (serialize_format (*it
));
255 for (FilenameStateList::iterator it
= filenames
.begin(); it
!= filenames
.end(); ++it
) {
256 root
.add_child_nocopy ((*it
)->filename
->get_state());
261 ExportProfileManager::serialize_local_profile (XMLNode
& root
)
263 for (TimespanStateList::iterator it
= timespans
.begin(); it
!= timespans
.end(); ++it
) {
264 root
.add_child_nocopy (serialize_timespan (*it
));
267 for (ChannelConfigStateList::iterator it
= channel_configs
.begin(); it
!= channel_configs
.end(); ++it
) {
268 root
.add_child_nocopy ((*it
)->config
->get_state());
272 std::vector
<sys::path
>
273 ExportProfileManager::find_file (std::string
const & pattern
)
275 vector
<sys::path
> found
;
277 Glib::PatternSpec
pattern_spec (pattern
);
278 find_matching_files_in_search_path (search_path
, pattern_spec
, found
);
284 ExportProfileManager::set_selection_range (nframes_t start
, nframes_t end
)
288 selection_range
.reset (new Location());
289 selection_range
->set_name (_("Selection"));
290 selection_range
->set (start
, end
);
292 selection_range
.reset();
295 for (TimespanStateList::iterator it
= timespans
.begin(); it
!= timespans
.end(); ++it
) {
296 (*it
)->selection_range
= selection_range
;
301 ExportProfileManager::set_single_range (nframes_t start
, nframes_t end
, Glib::ustring name
)
303 single_range_mode
= true;
305 single_range
.reset (new Location());
306 single_range
->set_name (name
);
307 single_range
->set (start
, end
);
311 return single_range
->id().to_s();
315 ExportProfileManager::init_timespans (XMLNodeList nodes
)
321 for (XMLNodeList::const_iterator it
= nodes
.begin(); it
!= nodes
.end(); ++it
) {
322 TimespanStatePtr span
= deserialize_timespan (**it
);
324 timespans
.push_back (span
);
325 } else { ok
= false; }
328 if (timespans
.empty()) {
329 TimespanStatePtr
timespan (new TimespanState (session_range
, selection_range
, ranges
));
330 timespans
.push_back (timespan
);
337 ExportProfileManager::TimespanStatePtr
338 ExportProfileManager::deserialize_timespan (XMLNode
& root
)
340 TimespanStatePtr
state (new TimespanState (session_range
, selection_range
, ranges
));
341 XMLProperty
const * prop
;
343 XMLNodeList spans
= root
.children ("Range");
344 for (XMLNodeList::iterator node_it
= spans
.begin(); node_it
!= spans
.end(); ++node_it
) {
346 prop
= (*node_it
)->property ("id");
347 if (!prop
) { continue; }
348 ustring id
= prop
->value();
350 for (LocationList::iterator it
= ranges
->begin(); it
!= ranges
->end(); ++it
) {
351 if ((!id
.compare ("session") && *it
== session_range
.get()) ||
352 (!id
.compare ("selection") && *it
== selection_range
.get()) ||
353 (!id
.compare ((*it
)->id().to_s()))) {
354 TimespanPtr timespan
= handler
->add_timespan();
355 timespan
->set_name ((*it
)->name());
356 timespan
->set_range_id (id
);
357 timespan
->set_range ((*it
)->start(), (*it
)->end());
358 state
->timespans
->push_back (timespan
);
363 if ((prop
= root
.property ("format"))) {
364 state
->time_format
= (TimeFormat
) string_2_enum (prop
->value(), TimeFormat
);
371 ExportProfileManager::serialize_timespan (TimespanStatePtr state
)
373 XMLNode
& root
= *(new XMLNode ("ExportTimespan"));
378 for (TimespanList::iterator it
= state
->timespans
->begin(); it
!= state
->timespans
->end(); ++it
) {
379 if ((span
= root
.add_child ("Range"))) {
380 span
->add_property ("id", (*it
)->range_id());
384 root
.add_property ("format", enum_2_string (state
->time_format
));
390 ExportProfileManager::update_ranges () {
393 if (single_range_mode
) {
394 ranges
->push_back (single_range
.get());
400 session_range
->set_name (_("Session"));
401 session_range
->set (session
.current_start_frame(), session
.current_end_frame());
402 ranges
->push_back (session_range
.get());
406 if (selection_range
) {
407 ranges
->push_back (selection_range
.get());
412 LocationList
const & list (session
.locations()->list());
413 for (LocationList::const_iterator it
= list
.begin(); it
!= list
.end(); ++it
) {
414 if ((*it
)->is_range_marker()) {
415 ranges
->push_back (*it
);
421 ExportProfileManager::init_channel_configs (XMLNodeList nodes
)
423 channel_configs
.clear();
426 ChannelConfigStatePtr
config (new ChannelConfigState (handler
->add_channel_config()));
427 channel_configs
.push_back (config
);
431 for (XMLNodeList::const_iterator it
= nodes
.begin(); it
!= nodes
.end(); ++it
) {
432 ChannelConfigStatePtr
config (new ChannelConfigState (handler
->add_channel_config()));
433 config
->config
->set_state (**it
);
434 channel_configs
.push_back (config
);
440 ExportProfileManager::FormatStatePtr
441 ExportProfileManager::duplicate_format_state (FormatStatePtr state
)
443 /* Note: The pointer in the new FormatState should point to the same format spec
444 as the original state's pointer. The spec itself should not be copied! */
446 FormatStatePtr
format (new FormatState (format_list
, state
->format
));
447 formats
.push_back (format
);
452 ExportProfileManager::remove_format_state (FormatStatePtr state
)
454 for (FormatStateList::iterator it
= formats
.begin(); it
!= formats
.end(); ++it
) {
463 ExportProfileManager::save_format_to_disk (FormatPtr format
)
465 // TODO filename character stripping
467 /* Get filename for file */
469 Glib::ustring new_name
= format
->name();
470 new_name
+= export_format_suffix
;
472 sys::path
new_path (export_config_dir
);
473 new_path
/= new_name
;
475 /* Check if format is on disk already */
476 FileMap::iterator it
;
477 if ((it
= format_file_map
.find (format
->id())) != format_file_map
.end()) {
479 /* Check if config is not in user config dir */
480 if (it
->second
.branch_path().to_string().compare (export_config_dir
.to_string())) {
484 XMLTree
tree (new_path
.to_string());
485 tree
.set_root (&format
->get_state());
490 /* Update file and rename if necessary */
492 XMLTree
tree (it
->second
.to_string());
493 tree
.set_root (&format
->get_state());
496 if (new_name
.compare (it
->second
.leaf())) {
497 sys::rename (it
->second
, new_path
);
501 it
->second
= new_path
;
506 XMLTree
tree (new_path
.to_string());
507 tree
.set_root (&format
->get_state());
511 FormatListChanged ();
516 ExportProfileManager::remove_format_profile (FormatPtr format
)
518 for (FormatList::iterator it
= format_list
->begin(); it
!= format_list
->end(); ++it
) {
520 format_list
->erase (it
);
525 FileMap::iterator it
= format_file_map
.find (format
->id());
526 if (it
!= format_file_map
.end()) {
527 sys::remove (it
->second
);
528 format_file_map
.erase (it
);
531 FormatListChanged ();
534 ExportProfileManager::FormatPtr
535 ExportProfileManager::get_new_format (FormatPtr original
)
539 format
.reset (new ExportFormatSpecification (*original
));
541 format
= handler
->add_format();
542 format
->set_name ("empty format");
545 sys::path path
= save_format_to_disk (format
);
546 FilePair
pair (format
->id(), path
);
547 format_file_map
.insert (pair
);
549 format_list
->push_back (format
);
550 FormatListChanged ();
556 ExportProfileManager::init_formats (XMLNodeList nodes
)
561 for (XMLNodeList::const_iterator it
= nodes
.begin(); it
!= nodes
.end(); ++it
) {
562 FormatStatePtr format
= deserialize_format (**it
);
564 formats
.push_back (format
);
565 } else { ok
= false; }
568 if (formats
.empty ()) {
569 FormatStatePtr
format (new FormatState (format_list
, FormatPtr ()));
570 formats
.push_back (format
);
577 ExportProfileManager::FormatStatePtr
578 ExportProfileManager::deserialize_format (XMLNode
& root
)
583 if ((prop
= root
.property ("id"))) {
587 for (FormatList::iterator it
= format_list
->begin(); it
!= format_list
->end(); ++it
) {
588 if ((*it
)->id() == id
) {
589 return FormatStatePtr (new FormatState (format_list
, *it
));
593 return FormatStatePtr ();
597 ExportProfileManager::serialize_format (FormatStatePtr state
)
599 XMLNode
* root
= new XMLNode ("ExportFormat");
601 string id
= state
->format
? state
->format
->id().to_s() : "";
602 root
->add_property ("id", id
);
608 ExportProfileManager::load_formats ()
610 vector
<sys::path
> found
= find_file (string_compose ("*%1", export_format_suffix
));
612 for (vector
<sys::path
>::iterator it
= found
.begin(); it
!= found
.end(); ++it
) {
613 load_format_from_disk (*it
);
618 ExportProfileManager::load_format_from_disk (PBD::sys::path
const & path
)
620 XMLTree
const tree (path
.to_string());
621 FormatPtr format
= handler
->add_format (*tree
.root());
623 /* Handle id to filename mapping and don't add duplicates to list */
625 FilePair
pair (format
->id(), path
);
626 if (format_file_map
.insert (pair
).second
) {
627 format_list
->push_back (format
);
630 FormatListChanged ();
633 ExportProfileManager::FilenameStatePtr
634 ExportProfileManager::duplicate_filename_state (FilenameStatePtr state
)
636 FilenameStatePtr
filename (new FilenameState (handler
->add_filename_copy (state
->filename
)));
637 filenames
.push_back (filename
);
642 ExportProfileManager::remove_filename_state (FilenameStatePtr state
)
644 for (FilenameStateList::iterator it
= filenames
.begin(); it
!= filenames
.end(); ++it
) {
646 filenames
.erase (it
);
653 ExportProfileManager::init_filenames (XMLNodeList nodes
)
657 for (XMLNodeList::const_iterator it
= nodes
.begin(); it
!= nodes
.end(); ++it
) {
658 FilenamePtr filename
= handler
->add_filename();
659 filename
->set_state (**it
);
660 filenames
.push_back (FilenameStatePtr (new FilenameState (filename
)));
663 if (filenames
.empty()) {
664 FilenameStatePtr
filename (new FilenameState (handler
->add_filename()));
665 filenames
.push_back (filename
);
672 boost::shared_ptr
<ExportProfileManager::Warnings
>
673 ExportProfileManager::get_warnings ()
675 boost::shared_ptr
<Warnings
> warnings (new Warnings ());
677 ChannelConfigStatePtr channel_config_state
= channel_configs
.front();
678 TimespanStatePtr timespan_state
= timespans
.front();
680 /*** Check "global" config ***/
682 TimespanListPtr timespans
= timespan_state
->timespans
;
683 ChannelConfigPtr channel_config
= channel_config_state
->config
;
685 /* Check Timespans are not empty */
687 if (timespans
->empty()) {
688 warnings
->errors
.push_back (_("No timespan has been selected!"));
691 /* Check channel config ports */
693 if (!channel_config
->all_channels_have_ports ()) {
694 warnings
->warnings
.push_back (_("Some channels are empty"));
697 /*** Check files ***/
699 FormatStateList::const_iterator format_it
;
700 FilenameStateList::const_iterator filename_it
;
701 for (format_it
= formats
.begin(), filename_it
= filenames
.begin();
702 format_it
!= formats
.end() && filename_it
!= filenames
.end();
703 ++format_it
, ++filename_it
) {
704 check_config (warnings
, timespan_state
, channel_config_state
, *format_it
, *filename_it
);
711 ExportProfileManager::check_config (boost::shared_ptr
<Warnings
> warnings
,
712 TimespanStatePtr timespan_state
,
713 ChannelConfigStatePtr channel_config_state
,
714 FormatStatePtr format_state
,
715 FilenameStatePtr filename_state
)
717 TimespanListPtr timespans
= timespan_state
->timespans
;
718 ChannelConfigPtr channel_config
= channel_config_state
->config
;
719 FormatPtr format
= format_state
->format
;
720 FilenamePtr filename
= filename_state
->filename
;
722 /* Check format and maximum channel count */
723 if (!format
|| !format
->type()) {
724 warnings
->errors
.push_back (_("No format selected!"));
725 } else if (!channel_config
->get_n_chans()) {
726 warnings
->errors
.push_back (_("All channels are empty!"));
727 } else if (!check_format (format
, channel_config
->get_n_chans())) {
728 warnings
->errors
.push_back (_("One or more of the selected formats is not compatible with this system!"));
729 } else if (format
->channel_limit() < channel_config
->get_n_chans()) {
730 warnings
->errors
.push_back
731 (string_compose (_("%1 supports only %2 channels, but you have %3 channels in your channel configuration"),
732 format
->format_name(),
733 format
->channel_limit(),
734 channel_config
->get_n_chans()));
737 if (!warnings
->errors
.empty()) { return; }
739 /* Check filenames */
741 // filename->include_timespan = (timespans->size() > 1); Disabled for now...
743 for (std::list
<TimespanPtr
>::iterator timespan_it
= timespans
->begin(); timespan_it
!= timespans
->end(); ++timespan_it
) {
744 filename
->set_timespan (*timespan_it
);
746 if (channel_config
->get_split()) {
747 filename
->include_channel
= true;
749 for (uint32_t chan
= 1; chan
<= channel_config
->get_n_chans(); ++chan
) {
750 filename
->set_channel (chan
);
752 Glib::ustring path
= filename
->get_path (format
);
754 if (sys::exists (sys::path (path
))) {
755 warnings
->conflicting_filenames
.push_back (path
);
760 Glib::ustring path
= filename
->get_path (format
);
762 if (sys::exists (sys::path (path
))) {
763 warnings
->conflicting_filenames
.push_back (path
);
770 ExportProfileManager::check_format (FormatPtr format
, uint32_t channels
)
772 switch (format
->type()) {
773 case ExportFormatBase::T_Sndfile
:
774 return check_sndfile_format (format
, channels
);
777 throw ExportFailed (X_("Invalid format given for ExportFileFactory::check!"));
782 ExportProfileManager::check_sndfile_format (FormatPtr format
, unsigned int channels
)
785 sf_info
.channels
= channels
;
786 sf_info
.samplerate
= format
->sample_rate ();
787 sf_info
.format
= format
->format_id () | format
->sample_format ();
789 return (sf_format_check (&sf_info
) == SF_TRUE
? true : false);
792 }; // namespace ARDOUR