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_formats_search_path.h"
33 #include "ardour/export_timespan.h"
34 #include "ardour/export_channel_configuration.h"
35 #include "ardour/export_filename.h"
36 #include "ardour/export_preset.h"
37 #include "ardour/export_handler.h"
38 #include "ardour/export_failed.h"
39 #include "ardour/filename_extensions.h"
40 #include "ardour/route.h"
41 #include "ardour/session.h"
42 #include "ardour/broadcast_info.h"
53 ExportProfileManager::ExportProfileManager (Session
& s
, std::string xml_node_name
)
54 : xml_node_name (xml_node_name
)
55 , handler (s
.get_export_handler())
58 , session_range (new Location (s
))
59 , ranges (new LocationList ())
60 , single_range_mode (false)
62 , format_list (new FormatList ())
64 /* Initialize path variables */
66 export_config_dir
= user_config_directory();
67 export_config_dir
/= "export";
69 search_path
+= export_formats_search_path();
71 info
<< string_compose (_("Searching for export formats in %1"), search_path
.to_string()) << endmsg
;
73 /* create export config directory if necessary */
75 if (!sys::exists (export_config_dir
)) {
76 sys::create_directory (export_config_dir
);
82 /* Initialize all lists with an empty config */
85 init_timespans (dummy
);
86 init_channel_configs (dummy
);
88 init_filenames (dummy
);
91 ExportProfileManager::~ExportProfileManager ()
93 if (single_range_mode
) { return; }
95 XMLNode
* instant_xml (new XMLNode (xml_node_name
));
96 serialize_profile (*instant_xml
);
97 session
.add_instant_xml (*instant_xml
, false);
101 ExportProfileManager::load_profile ()
103 XMLNode
* instant_node
= session
.instant_xml (xml_node_name
);
105 set_state (*instant_node
);
107 XMLNode
empty_node (xml_node_name
);
108 set_state (empty_node
);
113 ExportProfileManager::prepare_for_export ()
115 TimespanListPtr ts_list
= timespans
.front()->timespans
;
117 FormatStateList::const_iterator format_it
;
118 FilenameStateList::const_iterator filename_it
;
121 for (TimespanList::iterator ts_it
= ts_list
->begin(); ts_it
!= ts_list
->end(); ++ts_it
) {
122 // ..., each format-filename pair
123 for (format_it
= formats
.begin(), filename_it
= filenames
.begin();
124 format_it
!= formats
.end() && filename_it
!= filenames
.end();
125 ++format_it
, ++filename_it
) {
127 ExportFilenamePtr filename
= (*filename_it
)->filename
;
128 // filename->include_timespan = (ts_list->size() > 1); Disabled for now...
130 boost::shared_ptr
<BroadcastInfo
> b
;
131 if ((*format_it
)->format
->has_broadcast_info()) {
132 b
.reset (new BroadcastInfo
);
133 b
->set_from_session (session
, (*ts_it
)->get_start());
136 // ...and each channel config
137 filename
->include_channel_config
= (channel_configs
.size() > 1);
138 for(ChannelConfigStateList::iterator cc_it
= channel_configs
.begin(); cc_it
!= channel_configs
.end(); ++cc_it
) {
139 handler
->add_export_config (*ts_it
, (*cc_it
)->config
, (*format_it
)->format
, filename
, b
);
146 ExportProfileManager::load_preset (ExportPresetPtr preset
)
150 current_preset
= preset
;
151 if (!preset
) { return false; }
153 XMLNode
const * state
;
154 if ((state
= preset
->get_local_state())) {
155 set_local_state (*state
);
156 } else { ok
= false; }
158 if ((state
= preset
->get_global_state())) {
159 if (!set_global_state (*state
)) {
162 } else { ok
= false; }
168 ExportProfileManager::load_presets ()
170 vector
<sys::path
> found
= find_file (string_compose (X_("*%1"),export_preset_suffix
));
172 for (vector
<sys::path
>::iterator it
= found
.begin(); it
!= found
.end(); ++it
) {
173 load_preset_from_disk (*it
);
178 ExportProfileManager::preset_filename (std::string
const & preset_name
)
180 string safe_name
= legalize_for_path (preset_name
);
181 return export_config_dir
.to_string() + "/" + safe_name
+ export_preset_suffix
;
185 ExportProfileManager::new_preset (string
const & name
)
187 // Generate new ID and do regular save
188 string filename
= preset_filename (name
);
189 current_preset
.reset (new ExportPreset (filename
, session
));
190 preset_list
.push_back (current_preset
);
191 return save_preset (name
);
195 ExportProfileManager::save_preset (string
const & name
)
197 string filename
= preset_filename (name
);
199 if (!current_preset
) {
200 current_preset
.reset (new ExportPreset (filename
, session
));
201 preset_list
.push_back (current_preset
);
204 XMLNode
* global_preset
= new XMLNode ("ExportPreset");
205 XMLNode
* local_preset
= new XMLNode ("ExportPreset");
207 serialize_global_profile (*global_preset
);
208 serialize_local_profile (*local_preset
);
210 current_preset
->set_name (name
);
211 current_preset
->set_global_state (*global_preset
);
212 current_preset
->set_local_state (*local_preset
);
214 current_preset
->save (filename
);
216 return current_preset
;
220 ExportProfileManager::remove_preset ()
222 if (!current_preset
) { return; }
224 for (PresetList::iterator it
= preset_list
.begin(); it
!= preset_list
.end(); ++it
) {
225 if (*it
== current_preset
) {
226 preset_list
.erase (it
);
231 FileMap::iterator it
= preset_file_map
.find (current_preset
->id());
232 if (it
!= preset_file_map
.end()) {
233 sys::remove (it
->second
);
234 preset_file_map
.erase (it
);
237 current_preset
->remove_local();
238 current_preset
.reset();
242 ExportProfileManager::load_preset_from_disk (PBD::sys::path
const & path
)
244 ExportPresetPtr
preset (new ExportPreset (path
.to_string(), session
));
246 /* Handle id to filename mapping and don't add duplicates to list */
248 FilePair
pair (preset
->id(), path
);
249 if (preset_file_map
.insert (pair
).second
) {
250 preset_list
.push_back (preset
);
255 ExportProfileManager::set_state (XMLNode
const & root
)
257 return set_global_state (root
) & set_local_state (root
);
261 ExportProfileManager::set_global_state (XMLNode
const & root
)
263 return init_filenames (root
.children ("ExportFilename")) &
264 init_formats (root
.children ("ExportFormat"));
268 ExportProfileManager::set_local_state (XMLNode
const & root
)
270 return init_timespans (root
.children ("ExportTimespan")) &
271 init_channel_configs (root
.children ("ExportChannelConfiguration"));
275 ExportProfileManager::serialize_profile (XMLNode
& root
)
277 serialize_local_profile (root
);
278 serialize_global_profile (root
);
282 ExportProfileManager::serialize_global_profile (XMLNode
& root
)
284 for (FormatStateList::iterator it
= formats
.begin(); it
!= formats
.end(); ++it
) {
285 root
.add_child_nocopy (serialize_format (*it
));
288 for (FilenameStateList::iterator it
= filenames
.begin(); it
!= filenames
.end(); ++it
) {
289 root
.add_child_nocopy ((*it
)->filename
->get_state());
294 ExportProfileManager::serialize_local_profile (XMLNode
& root
)
296 for (TimespanStateList::iterator it
= timespans
.begin(); it
!= timespans
.end(); ++it
) {
297 root
.add_child_nocopy (serialize_timespan (*it
));
300 for (ChannelConfigStateList::iterator it
= channel_configs
.begin(); it
!= channel_configs
.end(); ++it
) {
301 root
.add_child_nocopy ((*it
)->config
->get_state());
305 std::vector
<sys::path
>
306 ExportProfileManager::find_file (std::string
const & pattern
)
308 vector
<sys::path
> found
;
310 Glib::PatternSpec
pattern_spec (pattern
);
311 find_matching_files_in_search_path (search_path
, pattern_spec
, found
);
317 ExportProfileManager::set_selection_range (framepos_t start
, framepos_t end
)
321 selection_range
.reset (new Location (session
));
322 selection_range
->set_name (_("Selection"));
323 selection_range
->set (start
, end
);
325 selection_range
.reset();
328 for (TimespanStateList::iterator it
= timespans
.begin(); it
!= timespans
.end(); ++it
) {
329 (*it
)->selection_range
= selection_range
;
334 ExportProfileManager::set_single_range (framepos_t start
, framepos_t end
, string name
)
336 single_range_mode
= true;
338 single_range
.reset (new Location (session
));
339 single_range
->set_name (name
);
340 single_range
->set (start
, end
);
344 return single_range
->id().to_s();
348 ExportProfileManager::init_timespans (XMLNodeList nodes
)
354 for (XMLNodeList::const_iterator it
= nodes
.begin(); it
!= nodes
.end(); ++it
) {
355 TimespanStatePtr span
= deserialize_timespan (**it
);
357 timespans
.push_back (span
);
358 } else { ok
= false; }
361 if (timespans
.empty()) {
362 TimespanStatePtr
state (new TimespanState (session_range
, selection_range
, ranges
));
363 timespans
.push_back (state
);
365 // Add session as default selection
366 ExportTimespanPtr timespan
= handler
->add_timespan();
367 timespan
->set_name (session_range
->name());
368 timespan
->set_range_id ("session");
369 timespan
->set_range (session_range
->start(), session_range
->end());
370 state
->timespans
->push_back (timespan
);
377 ExportProfileManager::TimespanStatePtr
378 ExportProfileManager::deserialize_timespan (XMLNode
& root
)
380 TimespanStatePtr
state (new TimespanState (session_range
, selection_range
, ranges
));
381 XMLProperty
const * prop
;
383 XMLNodeList spans
= root
.children ("Range");
384 for (XMLNodeList::iterator node_it
= spans
.begin(); node_it
!= spans
.end(); ++node_it
) {
386 prop
= (*node_it
)->property ("id");
387 if (!prop
) { continue; }
388 string id
= prop
->value();
390 for (LocationList::iterator it
= ranges
->begin(); it
!= ranges
->end(); ++it
) {
391 if ((!id
.compare ("session") && *it
== session_range
.get()) ||
392 (!id
.compare ("selection") && *it
== selection_range
.get()) ||
393 (!id
.compare ((*it
)->id().to_s()))) {
394 ExportTimespanPtr timespan
= handler
->add_timespan();
395 timespan
->set_name ((*it
)->name());
396 timespan
->set_range_id (id
);
397 timespan
->set_range ((*it
)->start(), (*it
)->end());
398 state
->timespans
->push_back (timespan
);
403 if ((prop
= root
.property ("format"))) {
404 state
->time_format
= (TimeFormat
) string_2_enum (prop
->value(), TimeFormat
);
411 ExportProfileManager::serialize_timespan (TimespanStatePtr state
)
413 XMLNode
& root
= *(new XMLNode ("ExportTimespan"));
418 for (TimespanList::iterator it
= state
->timespans
->begin(); it
!= state
->timespans
->end(); ++it
) {
419 if ((span
= root
.add_child ("Range"))) {
420 span
->add_property ("id", (*it
)->range_id());
424 root
.add_property ("format", enum_2_string (state
->time_format
));
430 ExportProfileManager::update_ranges () {
433 if (single_range_mode
) {
434 ranges
->push_back (single_range
.get());
440 session_range
->set_name (_("Session"));
441 session_range
->set (session
.current_start_frame(), session
.current_end_frame());
442 ranges
->push_back (session_range
.get());
446 if (selection_range
) {
447 ranges
->push_back (selection_range
.get());
452 LocationList
const & list (session
.locations()->list());
453 for (LocationList::const_iterator it
= list
.begin(); it
!= list
.end(); ++it
) {
454 if ((*it
)->is_range_marker()) {
455 ranges
->push_back (*it
);
460 ExportProfileManager::ChannelConfigStatePtr
461 ExportProfileManager::add_channel_config ()
463 ChannelConfigStatePtr
ptr(new ChannelConfigState(handler
->add_channel_config()));
464 channel_configs
.push_back(ptr
);
469 ExportProfileManager::init_channel_configs (XMLNodeList nodes
)
471 channel_configs
.clear();
474 ChannelConfigStatePtr
config (new ChannelConfigState (handler
->add_channel_config()));
475 channel_configs
.push_back (config
);
477 // Add master outs as default
478 IO
* master_out
= session
.master_out()->output().get();
479 if (!master_out
) { return false; }
481 for (uint32_t n
= 0; n
< master_out
->n_ports().n_audio(); ++n
) {
482 PortExportChannel
* channel
= new PortExportChannel ();
483 channel
->add_port (master_out
->audio (n
));
485 ExportChannelPtr
chan_ptr (channel
);
486 config
->config
->register_channel (chan_ptr
);
491 for (XMLNodeList::const_iterator it
= nodes
.begin(); it
!= nodes
.end(); ++it
) {
492 ChannelConfigStatePtr
config (new ChannelConfigState (handler
->add_channel_config()));
493 config
->config
->set_state (**it
);
494 channel_configs
.push_back (config
);
500 ExportProfileManager::FormatStatePtr
501 ExportProfileManager::duplicate_format_state (FormatStatePtr state
)
503 /* Note: The pointer in the new FormatState should point to the same format spec
504 as the original state's pointer. The spec itself should not be copied! */
506 FormatStatePtr
format (new FormatState (format_list
, state
->format
));
507 formats
.push_back (format
);
512 ExportProfileManager::remove_format_state (FormatStatePtr state
)
514 for (FormatStateList::iterator it
= formats
.begin(); it
!= formats
.end(); ++it
) {
523 ExportProfileManager::save_format_to_disk (ExportFormatSpecPtr format
)
525 // TODO filename character stripping
527 /* Get filename for file */
529 string new_name
= format
->name();
530 new_name
+= export_format_suffix
;
532 /* make sure its legal for the filesystem */
534 new_name
= legalize_for_path (new_name
);
536 sys::path
new_path (export_config_dir
);
537 new_path
/= new_name
;
539 /* Check if format is on disk already */
540 FileMap::iterator it
;
541 if ((it
= format_file_map
.find (format
->id())) != format_file_map
.end()) {
543 /* Check if config is not in user config dir */
544 if (it
->second
.branch_path().to_string().compare (export_config_dir
.to_string())) {
548 XMLTree
tree (new_path
.to_string());
549 tree
.set_root (&format
->get_state());
554 /* Update file and rename if necessary */
556 XMLTree
tree (it
->second
.to_string());
557 tree
.set_root (&format
->get_state());
560 if (new_name
.compare (it
->second
.leaf())) {
561 sys::rename (it
->second
, new_path
);
565 it
->second
= new_path
;
570 XMLTree
tree (new_path
.to_string());
571 tree
.set_root (&format
->get_state());
575 FormatListChanged ();
580 ExportProfileManager::remove_format_profile (ExportFormatSpecPtr format
)
582 for (FormatList::iterator it
= format_list
->begin(); it
!= format_list
->end(); ++it
) {
584 format_list
->erase (it
);
589 FileMap::iterator it
= format_file_map
.find (format
->id());
590 if (it
!= format_file_map
.end()) {
591 sys::remove (it
->second
);
592 format_file_map
.erase (it
);
595 FormatListChanged ();
599 ExportProfileManager::get_new_format (ExportFormatSpecPtr original
)
601 ExportFormatSpecPtr format
;
603 format
.reset (new ExportFormatSpecification (*original
));
605 format
= handler
->add_format();
606 format
->set_name ("empty format");
609 sys::path path
= save_format_to_disk (format
);
610 FilePair
pair (format
->id(), path
);
611 format_file_map
.insert (pair
);
613 format_list
->push_back (format
);
614 FormatListChanged ();
620 ExportProfileManager::init_formats (XMLNodeList nodes
)
625 for (XMLNodeList::const_iterator it
= nodes
.begin(); it
!= nodes
.end(); ++it
) {
626 FormatStatePtr format
= deserialize_format (**it
);
628 formats
.push_back (format
);
629 } else { ok
= false; }
632 if (formats
.empty ()) {
633 FormatStatePtr
format (new FormatState (format_list
, ExportFormatSpecPtr ()));
634 formats
.push_back (format
);
641 ExportProfileManager::FormatStatePtr
642 ExportProfileManager::deserialize_format (XMLNode
& root
)
647 if ((prop
= root
.property ("id"))) {
651 for (FormatList::iterator it
= format_list
->begin(); it
!= format_list
->end(); ++it
) {
652 if ((*it
)->id() == id
) {
653 return FormatStatePtr (new FormatState (format_list
, *it
));
657 return FormatStatePtr ();
661 ExportProfileManager::serialize_format (FormatStatePtr state
)
663 XMLNode
* root
= new XMLNode ("ExportFormat");
665 string id
= state
->format
? state
->format
->id().to_s() : "";
666 root
->add_property ("id", id
);
672 ExportProfileManager::load_formats ()
674 vector
<sys::path
> found
= find_file (string_compose ("*%1", export_format_suffix
));
676 for (vector
<sys::path
>::iterator it
= found
.begin(); it
!= found
.end(); ++it
) {
677 load_format_from_disk (*it
);
682 ExportProfileManager::load_format_from_disk (PBD::sys::path
const & path
)
684 XMLTree
const tree (path
.to_string());
685 ExportFormatSpecPtr format
= handler
->add_format (*tree
.root());
687 /* Handle id to filename mapping and don't add duplicates to list */
689 FilePair
pair (format
->id(), path
);
690 if (format_file_map
.insert (pair
).second
) {
691 format_list
->push_back (format
);
694 FormatListChanged ();
697 ExportProfileManager::FilenameStatePtr
698 ExportProfileManager::duplicate_filename_state (FilenameStatePtr state
)
700 FilenameStatePtr
filename (new FilenameState (handler
->add_filename_copy (state
->filename
)));
701 filenames
.push_back (filename
);
706 ExportProfileManager::remove_filename_state (FilenameStatePtr state
)
708 for (FilenameStateList::iterator it
= filenames
.begin(); it
!= filenames
.end(); ++it
) {
710 filenames
.erase (it
);
717 ExportProfileManager::init_filenames (XMLNodeList nodes
)
721 for (XMLNodeList::const_iterator it
= nodes
.begin(); it
!= nodes
.end(); ++it
) {
722 ExportFilenamePtr filename
= handler
->add_filename();
723 filename
->set_state (**it
);
724 filenames
.push_back (FilenameStatePtr (new FilenameState (filename
)));
727 if (filenames
.empty()) {
728 FilenameStatePtr
filename (new FilenameState (handler
->add_filename()));
729 filenames
.push_back (filename
);
736 boost::shared_ptr
<ExportProfileManager::Warnings
>
737 ExportProfileManager::get_warnings ()
739 boost::shared_ptr
<Warnings
> warnings (new Warnings ());
741 ChannelConfigStatePtr channel_config_state
;
742 if (!channel_configs
.empty ()) {
743 channel_config_state
= channel_configs
.front();
746 TimespanStatePtr timespan_state
= timespans
.front();
748 /*** Check "global" config ***/
750 TimespanListPtr timespans
= timespan_state
->timespans
;
752 ExportChannelConfigPtr channel_config
;
753 if (channel_config_state
) {
754 channel_config
= channel_config_state
->config
;
757 /* Check Timespans are not empty */
759 if (timespans
->empty()) {
760 warnings
->errors
.push_back (_("No timespan has been selected!"));
763 if (channel_config_state
== 0) {
764 warnings
->errors
.push_back (_("No channels have been selected!"));
766 /* Check channel config ports */
767 if (!channel_config
->all_channels_have_ports ()) {
768 warnings
->warnings
.push_back (_("Some channels are empty"));
772 /*** Check files ***/
774 if (channel_config_state
) {
775 FormatStateList::const_iterator format_it
;
776 FilenameStateList::const_iterator filename_it
;
777 for (format_it
= formats
.begin(), filename_it
= filenames
.begin();
778 format_it
!= formats
.end() && filename_it
!= filenames
.end();
779 ++format_it
, ++filename_it
) {
780 check_config (warnings
, timespan_state
, channel_config_state
, *format_it
, *filename_it
);
788 ExportProfileManager::check_config (boost::shared_ptr
<Warnings
> warnings
,
789 TimespanStatePtr timespan_state
,
790 ChannelConfigStatePtr channel_config_state
,
791 FormatStatePtr format_state
,
792 FilenameStatePtr filename_state
)
794 TimespanListPtr timespans
= timespan_state
->timespans
;
795 ExportChannelConfigPtr channel_config
= channel_config_state
->config
;
796 ExportFormatSpecPtr format
= format_state
->format
;
797 ExportFilenamePtr filename
= filename_state
->filename
;
799 /* Check format and maximum channel count */
800 if (!format
|| !format
->type()) {
801 warnings
->errors
.push_back (_("No format selected!"));
802 } else if (!channel_config
->get_n_chans()) {
803 warnings
->errors
.push_back (_("All channels are empty!"));
804 } else if (!check_format (format
, channel_config
->get_n_chans())) {
805 warnings
->errors
.push_back (_("One or more of the selected formats is not compatible with this system!"));
806 } else if (format
->channel_limit() < channel_config
->get_n_chans()) {
807 warnings
->errors
.push_back
808 (string_compose (_("%1 supports only %2 channels, but you have %3 channels in your channel configuration"),
809 format
->format_name(),
810 format
->channel_limit(),
811 channel_config
->get_n_chans()));
814 if (!warnings
->errors
.empty()) { return; }
816 /* Check filenames */
818 // filename->include_timespan = (timespans->size() > 1); Disabled for now...
820 for (std::list
<ExportTimespanPtr
>::iterator timespan_it
= timespans
->begin(); timespan_it
!= timespans
->end(); ++timespan_it
) {
821 filename
->set_timespan (*timespan_it
);
823 if (channel_config
->get_split()) {
824 filename
->include_channel
= true;
826 for (uint32_t chan
= 1; chan
<= channel_config
->get_n_chans(); ++chan
) {
827 filename
->set_channel (chan
);
829 string path
= filename
->get_path (format
);
831 if (sys::exists (sys::path (path
))) {
832 warnings
->conflicting_filenames
.push_back (path
);
837 string path
= filename
->get_path (format
);
839 if (sys::exists (sys::path (path
))) {
840 warnings
->conflicting_filenames
.push_back (path
);
847 ExportProfileManager::check_format (ExportFormatSpecPtr format
, uint32_t channels
)
849 switch (format
->type()) {
850 case ExportFormatBase::T_Sndfile
:
851 return check_sndfile_format (format
, channels
);
854 throw ExportFailed (X_("Invalid format given for ExportFileFactory::check!"));
859 ExportProfileManager::check_sndfile_format (ExportFormatSpecPtr format
, unsigned int channels
)
862 sf_info
.channels
= channels
;
863 sf_info
.samplerate
= format
->sample_rate ();
864 sf_info
.format
= format
->format_id () | format
->sample_format ();
866 return (sf_format_check (&sf_info
) == SF_TRUE
? true : false);
869 }; // namespace ARDOUR