initial implementation of "bring all media into session folder". Incomplete but basically functional for audio files

This commit is contained in:
Paul Davis 2014-07-08 00:53:06 -04:00
parent d3e3f5f005
commit fcabd5d8ee
13 changed files with 339 additions and 72 deletions

View File

@ -25,10 +25,11 @@
<menuitem action='CloseVideo'/>
<menu name='Export' action='Export'>
<menuitem action='ExportAudio'/>
<menuitem action='ExportAudio'/>
<menuitem action='StemExport'/>
<menuitem action='ExportVideo'/>
</menu>
<menuitem action='bring-into-session'/>
<menu name='Cleanup' action='Cleanup'>
<menuitem action='CleanupUnused'/>
<menuitem action='FlushWastebasket'/>

View File

@ -2099,6 +2099,10 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
TimeAxisView* _stepping_axis_view;
void zoom_vertical_modifier_released();
void bring_in_callback (Gtk::Label*, uint32_t n, uint32_t total, std::string name);
void update_bring_in_message (Gtk::Label* label, uint32_t n, uint32_t total, std::string name);
void bring_all_sources_into_session ();
friend class Drag;
friend class RegionDrag;
friend class RegionMoveDrag;

View File

@ -701,6 +701,10 @@ Editor::register_actions ()
act = ActionManager::register_action (editor_actions, X_("importFromSession"), _("Import From Session"), sigc::mem_fun(*this, &Editor::session_import_dialog));
ActionManager::write_sensitive_actions.push_back (act);
act = ActionManager::register_action (editor_actions, X_("bring-into-session"), _("Bring all media into session folder"), sigc::mem_fun(*this, &Editor::bring_all_sources_into_session));
ActionManager::write_sensitive_actions.push_back (act);
ActionManager::register_toggle_action (editor_actions, X_("ToggleSummary"), _("Show Summary"), sigc::mem_fun (*this, &Editor::set_summary));
ActionManager::register_toggle_action (editor_actions, X_("ToggleGroupTabs"), _("Show Group Tabs"), sigc::mem_fun (*this, &Editor::set_group_tabs));

View File

@ -7045,3 +7045,39 @@ Editor::unlock ()
start_lock_event_timing ();
}
}
void
Editor::bring_in_callback (Gtk::Label* label, uint32_t n, uint32_t total, string name)
{
Gtkmm2ext::UI::instance()->call_slot (invalidator (*this), boost::bind (&Editor::update_bring_in_message, this, label, n, total, name));
}
void
Editor::update_bring_in_message (Gtk::Label* label, uint32_t n, uint32_t total, string name)
{
label->set_text (string_compose ("Copying %1, %2 of %3", name, n, total));
Gtkmm2ext::UI::instance()->flush_pending ();
}
void
Editor::bring_all_sources_into_session ()
{
if (!_session) {
return;
}
Gtk::Label msg;
ArdourDialog w (_("Moving embedded files into session folder"));
w.get_vbox()->pack_start (msg);
w.present ();
/* flush all pending GUI events because we're about to start copying
* files
*/
Gtkmm2ext::UI::instance()->flush_pending ();
cerr << " Do it\n";
_session->bring_all_sources_into_session (boost::bind (&Editor::bring_in_callback, this, &msg, _1, _2, _3));
}

View File

@ -89,6 +89,8 @@ public:
*/
int rename (const std::string& name);
virtual void release_descriptor () {}
protected:
FileSource (Session& session, DataType type,
const std::string& path,

View File

@ -196,11 +196,16 @@ class LIBARDOUR_API Session : public PBD::StatefulDestructible, public PBD::Scop
std::string peak_path (std::string) const;
std::string peak_path_from_audio_path (std::string) const;
bool audio_source_name_is_unique (const std::string& name, uint32_t chan);
std::string format_audio_source_name (const std::string& legalized_base, uint32_t nchan, uint32_t chan, bool destructive, bool take_required, uint32_t cnt, bool related_exists);
std::string new_audio_source_path_for_embedded (const std::string& existing_path);
std::string new_audio_source_path (const std::string&, uint32_t nchans, uint32_t chan, bool destructive, bool take_required);
std::string new_midi_source_path (const std::string&);
RouteList new_route_from_template (uint32_t how_many, const std::string& template_path, const std::string& name);
std::vector<std::string> get_paths_for_new_sources (bool allow_replacing, const std::string& import_file_path, uint32_t channels);
int bring_all_sources_into_session (boost::function<void(uint32_t,uint32_t,std::string)> callback);
void process (pframes_t nframes);
BufferSet& get_silent_buffers (ChanCount count = ChanCount::ZERO);
@ -863,6 +868,7 @@ class LIBARDOUR_API Session : public PBD::StatefulDestructible, public PBD::Scop
std::vector<std::string> source_search_path(DataType) const;
void ensure_search_path_includes (const std::string& path, DataType type);
void remove_dir_from_search_path (const std::string& path, DataType type);
std::list<std::string> unknown_processors () const;

View File

@ -75,6 +75,8 @@ class LIBARDOUR_API SndFileSource : public AudioFileSource {
static int get_soundfile_info (const std::string& path, SoundFileInfo& _info, std::string& error_msg);
void release_descriptor ();
protected:
void set_path (const std::string& p);
void set_header_timeline_position ();

View File

@ -32,6 +32,7 @@
#include "pbd/convert.h"
#include "pbd/basename.h"
#include "pbd/file_utils.h"
#include "pbd/mountpoint.h"
#include "pbd/stl_delete.h"
#include "pbd/strsplit.h"
@ -413,3 +414,4 @@ AudioFileSource::get_interleave_buffer (framecnt_t size)
return ssb->buf;
}

View File

@ -546,6 +546,12 @@ void
FileSource::set_path (const std::string& newpath)
{
_path = newpath;
set_within_session_from_path (newpath);
if (_within_session) {
_origin = Glib::path_get_basename (newpath);
} else {
_origin = newpath;
}
}
void
@ -597,3 +603,5 @@ FileSource::rename (const string& newpath)
return 0;
}

View File

@ -44,6 +44,7 @@
#include "pbd/stacktrace.h"
#include "pbd/file_utils.h"
#include "pbd/convert.h"
#include "pbd/md5.h"
#include "pbd/unwind.h"
#include "pbd/search_path.h"
@ -3442,6 +3443,146 @@ Session::peak_path (string base) const
return Glib::build_filename (_session_dir->peak_path(), base + peakfile_suffix);
}
string
Session::new_audio_source_path_for_embedded (const std::string& path)
{
/* embedded source:
*
* we know that the filename is already unique because it exists
* out in the filesystem.
*
* However, when we bring it into the session, we could get a
* collision.
*
* Eg. two embedded files:
*
* /foo/bar/baz.wav
* /frob/nic/baz.wav
*
* When merged into session, these collide.
*
* There will not be a conflict with in-memory sources
* because when the source was created we already picked
* a unique name for it.
*
* This collision is not likely to be common, but we have to guard
* against it. So, if there is a collision, take the md5 hash of the
* the path, and use that as the filename instead.
*/
SessionDirectory sdir (get_best_session_directory_for_new_audio());
string base = Glib::path_get_basename (path);
string newpath = Glib::build_filename (sdir.sound_path(), base);
if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
MD5 md5;
md5.digestString (path.c_str());
md5.writeToString ();
base = md5.digestChars;
/* XXX base needs suffix from path */
newpath = Glib::build_filename (sdir.sound_path(), base);
/* if this collides, we're screwed */
if (Glib::file_test (newpath, Glib::FILE_TEST_EXISTS)) {
error << string_compose (_("Merging embedded file %1: name collision AND md5 hash collision!"), path) << endmsg;
return string();
}
}
return newpath;
}
bool
Session::audio_source_name_is_unique (const string& name, uint32_t chan)
{
std::vector<string> sdirs = source_search_path (DataType::AUDIO);
vector<space_and_path>::iterator i;
uint32_t existing = 0;
string basename = PBD::basename_nosuffix (name);
for (vector<string>::const_iterator i = sdirs.begin(); i != sdirs.end(); ++i) {
/* note that we search *without* the extension so that
we don't end up both "Audio 1-1.wav" and "Audio 1-1.caf"
in the event that this new name is required for
a file format change.
*/
const string spath = *i;
if (matching_unsuffixed_filename_exists_in (spath, basename)) {
existing++;
break;
}
/* it is possible that we have the path already
* assigned to a source that has not yet been written
* (ie. the write source for a diskstream). we have to
* check this in order to make sure that our candidate
* path isn't used again, because that can lead to
* two Sources point to the same file with different
* notions of their removability.
*/
string possible_path = Glib::build_filename (spath, name);
if (audio_source_by_path_and_channel (possible_path, chan)) {
existing++;
break;
}
}
return (existing == 0);
}
string
Session::format_audio_source_name (const string& legalized_base, uint32_t nchan, uint32_t chan, bool destructive, bool take_required, uint32_t cnt, bool related_exists)
{
ostringstream sstr;
const string ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO);
if (destructive) {
sstr << 'T';
sstr << setfill ('0') << setw (4) << cnt;
sstr << legalized_base;
} else {
sstr << legalized_base;
if (take_required || related_exists) {
sstr << '-';
sstr << cnt;
}
}
if (nchan == 2) {
if (chan == 0) {
sstr << "%L";
} else {
sstr << "%R";
}
} else if (nchan > 2) {
if (nchan < 26) {
sstr << '%';
sstr << 'a' + chan;
} else {
/* XXX what? more than 26 channels! */
sstr << '%';
sstr << chan+1;
}
}
sstr << ext;
return sstr.str();
}
/** Return a unique name based on \a base for a new internal audio source */
string
Session::new_audio_source_path (const string& base, uint32_t nchan, uint32_t chan, bool destructive, bool take_required)
@ -3450,86 +3591,20 @@ Session::new_audio_source_path (const string& base, uint32_t nchan, uint32_t cha
string possible_name;
const uint32_t limit = 9999; // arbitrary limit on number of files with the same basic name
string legalized;
string ext = native_header_format_extension (config.get_native_file_header_format(), DataType::AUDIO);
bool some_related_source_name_exists = false;
possible_name[0] = '\0';
legalized = legalize_for_path (base);
std::vector<string> sdirs = source_search_path(DataType::AUDIO);
// Find a "version" of the base name that doesn't exist in any of the possible directories.
for (cnt = (destructive ? ++destructive_index : 1); cnt <= limit; ++cnt) {
vector<space_and_path>::iterator i;
uint32_t existing = 0;
for (vector<string>::const_iterator i = sdirs.begin(); i != sdirs.end(); ++i) {
ostringstream sstr;
if (destructive) {
sstr << 'T';
sstr << setfill ('0') << setw (4) << cnt;
sstr << legalized;
} else {
sstr << legalized;
if (take_required || some_related_source_name_exists) {
sstr << '-';
sstr << cnt;
}
}
if (nchan == 2) {
if (chan == 0) {
sstr << "%L";
} else {
sstr << "%R";
}
} else if (nchan > 2 && nchan < 26) {
sstr << '%';
sstr << 'a' + chan;
}
sstr << ext;
possible_name = sstr.str();
const string spath = (*i);
/* note that we search *without* the extension so that
we don't end up both "Audio 1-1.wav" and "Audio 1-1.caf"
in the event that this new name is required for
a file format change.
*/
if (matching_unsuffixed_filename_exists_in (spath, possible_name)) {
existing++;
break;
}
/* it is possible that we have the path already
* assigned to a source that has not yet been written
* (ie. the write source for a diskstream). we have to
* check this in order to make sure that our candidate
* path isn't used again, because that can lead to
* two Sources point to the same file with different
* notions of their removability.
*/
string possible_path = Glib::build_filename (spath, possible_name);
if (audio_source_by_path_and_channel (possible_path, chan)) {
existing++;
break;
}
}
if (existing == 0) {
possible_name = format_audio_source_name (legalized, nchan, chan, destructive, take_required, cnt, some_related_source_name_exists);
if (audio_source_name_is_unique (possible_name, chan)) {
break;
}
some_related_source_name_exists = true;
if (cnt > limit) {
@ -4776,6 +4851,33 @@ Session::ensure_search_path_includes (const string& path, DataType type)
}
}
void
Session::remove_dir_from_search_path (const string& dir, DataType type)
{
Searchpath sp;
switch (type) {
case DataType::AUDIO:
sp = Searchpath(config.get_audio_search_path ());
break;
case DataType::MIDI:
sp = Searchpath (config.get_midi_search_path ());
break;
}
sp -= dir;
switch (type) {
case DataType::AUDIO:
config.set_audio_search_path (sp.to_string());
break;
case DataType::MIDI:
config.set_midi_search_path (sp.to_string());
break;
}
}
boost::shared_ptr<Speakers>
Session::get_speakers()
{

View File

@ -3773,3 +3773,92 @@ Session::get_info_from_path (const string& xmlpath, float& sample_rate, SampleFo
return !(found_sr && found_data_format); // zero if they are both found
}
typedef std::vector<boost::shared_ptr<FileSource> > SeveralFileSources;
typedef std::map<std::string,SeveralFileSources> SourcePathMap;
int
Session::bring_all_sources_into_session (boost::function<void(uint32_t,uint32_t,string)> callback)
{
uint32_t total = 0;
uint32_t n = 0;
SourcePathMap source_path_map;
string new_path;
boost::shared_ptr<AudioFileSource> afs;
int ret = 0;
{
Glib::Threads::Mutex::Lock lm (source_lock);
cerr << " total sources = " << sources.size();
for (SourceMap::const_iterator i = sources.begin(); i != sources.end(); ++i) {
boost::shared_ptr<FileSource> fs = boost::dynamic_pointer_cast<FileSource> (i->second);
if (!fs) {
continue;
}
if (fs->within_session()) {
cerr << "skip " << fs->name() << endl;
continue;
}
if (source_path_map.find (fs->path()) != source_path_map.end()) {
source_path_map[fs->path()].push_back (fs);
} else {
SeveralFileSources v;
v.push_back (fs);
source_path_map.insert (make_pair (fs->path(), v));
}
total++;
}
cerr << " fsources = " << total << endl;
for (SourcePathMap::iterator i = source_path_map.begin(); i != source_path_map.end(); ++i) {
/* tell caller where we are */
string old_path = i->first;
callback (n, total, old_path);
cerr << old_path << endl;
new_path.clear ();
switch (i->second.front()->type()) {
case DataType::AUDIO:
new_path = new_audio_source_path_for_embedded (old_path);
break;
case DataType::MIDI:
break;
}
cerr << "Move " << old_path << " => " << new_path << endl;
if (!copy_file (old_path, new_path)) {
cerr << "failed !\n";
ret = -1;
}
/* make sure we stop looking in the external
dir/folder. Remember, this is an all-or-nothing
operations, it doesn't merge just some files.
*/
remove_dir_from_search_path (Glib::path_get_dirname (old_path), i->second.front()->type());
for (SeveralFileSources::iterator f = i->second.begin(); f != i->second.end(); ++f) {
(*f)->set_path (new_path);
}
}
}
save_state ("", false, false);
return ret;
}

View File

@ -26,6 +26,7 @@
#include <errno.h>
#include <regex.h>
#include "pbd/file_utils.h"
#include "pbd/stl_delete.h"
#include "pbd/strsplit.h"
@ -717,4 +718,5 @@ SMFSource::prevent_deletion ()
_flags = Flag (_flags & ~(Removable|RemovableIfEmpty|RemoveAtDestroy));
}

View File

@ -1016,3 +1016,12 @@ SndFileSource::set_path (const string& p)
_descriptor->set_path (_path);
}
}
void
SndFileSource::release_descriptor ()
{
if (_descriptor) {
_descriptor->release ();
_descriptor = 0;
}
}