add support for browsing local filesystem

This commit is contained in:
unK
2008-10-27 20:35:02 +01:00
parent e1202f2dfe
commit beee0bc9d3
15 changed files with 346 additions and 170 deletions

View File

@@ -1,12 +1,13 @@
bin_PROGRAMS = ncmpcpp
ncmpcpp_SOURCES = color_parser.cpp help.cpp helpers.cpp libmpdclient.c \
lyrics.cpp menu.cpp misc.cpp mpdpp.cpp ncmpcpp.cpp scrollpad.cpp search_engine.cpp \
settings.cpp song.cpp status_checker.cpp str_pool.c tag_editor.cpp window.cpp
ncmpcpp_SOURCES = browser.cpp color_parser.cpp help.cpp helpers.cpp \
libmpdclient.c lyrics.cpp menu.cpp misc.cpp mpdpp.cpp ncmpcpp.cpp scrollpad.cpp \
search_engine.cpp settings.cpp song.cpp status_checker.cpp str_pool.c tag_editor.cpp \
window.cpp
# set the include path found by configure
INCLUDES= $(all_includes)
# the library search path.
ncmpcpp_LDFLAGS = $(all_libraries)
noinst_HEADERS = help.h helpers.h lyrics.h menu.h mpdpp.h scrollpad.h \
noinst_HEADERS = browser.h help.h helpers.h lyrics.h menu.h mpdpp.h scrollpad.h \
search_engine.h settings.h song.h status_checker.h tag_editor.h window.h

211
src/browser.cpp Normal file
View File

@@ -0,0 +1,211 @@
/***************************************************************************
* Copyright (C) 2008 by Andrzej Rybczak *
* electricityispower@gmail.com *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include <dirent.h>
#include <sys/stat.h>
#include <algorithm>
#include "browser.h"
#include "helpers.h"
#include "settings.h"
#ifdef HAVE_TAGLIB_H
# include "tag_editor.h"
#endif // HAVE_TAGLIB_H
extern MPDConnection *Mpd;
extern ncmpcpp_config Config;
extern Menu<Song> *mPlaylist;
extern Menu<Item> *mBrowser;
extern NcmpcppScreen current_screen;
extern string browsed_dir;
extern int browsed_dir_scroll_begin;
namespace
{
const string supported_extensions[] = { "wma", "asf", "rm", "mp1", "mp2", "mp3", "mp4", "m4a", "flac", "ogg", "wav", "au", "aiff", "aif", "ac3", "aac", "mpc", "it", "mod", "s3m", "xm", "wv", "." };
bool hasSupportedExtension(const string &file)
{
unsigned int last_dot = file.find_last_of(".");
if (last_dot > file.length())
return false;
string ext = file.substr(last_dot+1);
for (int i = 0; supported_extensions[i] != "."; i++)
if (ext == supported_extensions[i])
return true;
return false;
}
void GetLocalDirectory(const string &dir, ItemList &v)
{
dirent **list;
int n = scandir(dir.c_str(), &list, NULL, alphasort);
if (n < 2)
{
delete list;
return;
}
struct stat file_stat;
string full_path;
for (int i = 2; i < n; i++)
{
Item new_item;
full_path = dir;
if (dir != "/")
full_path += "/";
full_path += list[i]->d_name;
stat(full_path.c_str(), &file_stat);
if (S_ISDIR(file_stat.st_mode))
{
new_item.type = itDirectory;
new_item.name = full_path;
v.push_back(new_item);
}
else if (hasSupportedExtension(list[i]->d_name))
{
new_item.type = itSong;
mpd_Song *s = mpd_newSong();
s->file = str_pool_get(full_path.c_str());
# ifdef HAVE_TAGLIB_H
ReadTagsFromFile(s);
# endif // HAVE_TAGLIB_H
new_item.song = new Song(s);
v.push_back(new_item);
}
delete list[i];
}
delete list;
}
}
void UpdateItemList(Menu<Item> *menu)
{
bool bold = 0;
for (int i = 0; i < menu->Size(); i++)
{
if (menu->at(i).type == itSong)
{
for (int j = 0; j < mPlaylist->Size(); j++)
{
if (mPlaylist->at(j).GetHash() == menu->at(i).song->GetHash())
{
bold = 1;
break;
}
}
menu->BoldOption(i, bold);
bold = 0;
}
}
menu->Refresh();
}
string DisplayItem(const Item &item, void *, const Menu<Item> *menu)
{
switch (item.type)
{
case itDirectory:
{
if (item.song)
return "[..]";
int slash = item.name.find_last_of("/");
return "[" + (slash != string::npos ? item.name.substr(slash+1) : item.name) + "]";
}
case itSong:
// I know casting that way is ugly etc., but it works.
return DisplaySong(*item.song, &Config.song_list_format, (const Menu<Song> *)menu);
case itPlaylist:
return Config.browser_playlist_prefix + item.name;
default:
return "";
}
}
void GetDirectory(string dir, string subdir)
{
if (dir.empty())
dir = "/";
int highlightme = -1;
browsed_dir_scroll_begin = 0;
if (browsed_dir != dir)
mBrowser->Reset();
browsed_dir = dir;
mBrowser->Clear(0);
if (dir != "/")
{
Item parent;
int slash = dir.find_last_of("/");
parent.song = (Song *) 1; // in that way we assume that's really parent dir
parent.name = slash != string::npos ? dir.substr(0, slash) : "/";
parent.type = itDirectory;
mBrowser->AddOption(parent);
}
ItemList list;
Config.local_browser ? GetLocalDirectory(dir, list) : Mpd->GetDirectory(dir, list);
sort(list.begin(), list.end(), CaseInsensitiveSorting());
for (ItemList::iterator it = list.begin(); it != list.end(); it++)
{
switch (it->type)
{
case itPlaylist:
{
mBrowser->AddOption(*it);
break;
}
case itDirectory:
{
if (it->name == subdir)
highlightme = mBrowser->Size();
mBrowser->AddOption(*it);
break;
}
case itSong:
{
bool bold = 0;
for (int i = 0; i < mPlaylist->Size(); i++)
{
if (mPlaylist->at(i).GetHash() == it->song->GetHash())
{
bold = 1;
break;
}
}
mBrowser->AddOption(*it, bold);
break;
}
}
}
mBrowser->Highlight(highlightme);
if (current_screen == csBrowser)
mBrowser->Hide();
}

34
src/browser.h Normal file
View File

@@ -0,0 +1,34 @@
/***************************************************************************
* Copyright (C) 2008 by Andrzej Rybczak *
* electricityispower@gmail.com *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef HAVE_BROWSER_H
#define HAVE_BROWSER_H
#include "mpdpp.h"
#include "ncmpcpp.h"
void UpdateItemList(Menu<Item> *);
string DisplayItem(const Item &, void *, const Menu<Item> *);
void GetDirectory(string, string = "/");
#endif

View File

@@ -18,9 +18,13 @@
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "mpdpp.h"
#include "help.h"
#include "settings.h"
extern MPDConnection *Mpd;
extern ncmpcpp_keys Key;
namespace
@@ -165,6 +169,8 @@ string GetKeybindings()
result += " [.b]Keys - Browse screen\n -----------------------------------------[/b]\n";
result += DisplayKeys(Key.Enter) + "Enter directory/Add item to playlist and play\n";
result += DisplayKeys(Key.Space) + "Add item to playlist\n";
if (Mpd->GetHostname()[0] == '/') // are we connected to unix socket?
result += DisplayKeys(Key.SwitchTagTypeList) + "Browse MPD database/local filesystem\n";
result += DisplayKeys(Key.GoToParentDir) + "Go to parent directory\n";
result += DisplayKeys(Key.Delete) + "Delete playlist\n\n\n";

View File

@@ -35,12 +35,9 @@ extern Window *wFooter;
extern NcmpcppScreen current_screen;
extern int lock_statusbar_delay;
extern int browsed_dir_scroll_begin;
extern time_t time_of_statusbar_lock;
extern string browsed_dir;
extern bool messages_allowed;
extern bool block_progressbar_update;
extern bool block_statusbar_update;
@@ -220,28 +217,6 @@ bool CaseInsensitiveSorting::operator()(const Item &a, const Item &b)
return a.type < b.type;
}
void UpdateItemList(Menu<Item> *menu)
{
bool bold = 0;
for (int i = 0; i < menu->Size(); i++)
{
if (menu->at(i).type == itSong)
{
for (int j = 0; j < mPlaylist->Size(); j++)
{
if (mPlaylist->at(j).GetHash() == menu->at(i).song->GetHash())
{
bold = 1;
break;
}
}
menu->BoldOption(i, bold);
bold = 0;
}
}
menu->Refresh();
}
void UpdateSongList(Menu<Song> *menu)
{
bool bold = 0;
@@ -395,27 +370,6 @@ string DisplayStringPair(const StringPair &pair, void *, const Menu<StringPair>
return pair.first;
}
string DisplayItem(const Item &item, void *, const Menu<Item> *menu)
{
switch (item.type)
{
case itDirectory:
{
if (item.song)
return "[..]";
int slash = item.name.find_last_of("/");
return "[" + (slash != string::npos ? item.name.substr(slash+1) : item.name) + "]";
}
case itSong:
// I know casting that way is ugly etc., but it works.
return DisplaySong(*item.song, &Config.song_list_format, (const Menu<Song> *)menu);
case itPlaylist:
return Config.browser_playlist_prefix + item.name;
default:
return "";
}
}
string DisplayColumns(string song_template)
{
vector<string> cols;
@@ -577,7 +531,11 @@ string DisplaySongInColumns(const Song &s, void *s_template, const Menu<Song> *)
v = TO_WSTRING(Window::OmitBBCodes(ss)).substr(0, width-1);
for (int i = v.length(); i < width; i++, v += space) { }
if (!color.empty())
result += open_col + color + close_col;
{
result += open_col;
result += color;
result += close_col;
}
result += v;
if (!color.empty())
result += close_col2;
@@ -913,64 +871,3 @@ void ShowMessage(const string &message, int delay)
}
}
void GetDirectory(string dir, string subdir)
{
int highlightme = -1;
browsed_dir_scroll_begin = 0;
if (browsed_dir != dir)
mBrowser->Reset();
browsed_dir = dir;
mBrowser->Clear(0);
if (dir != "/")
{
Item parent;
int slash = dir.find_last_of("/");
parent.song = (Song *) 1; // in that way we assume that's really parent dir
parent.name = slash != string::npos ? dir.substr(0, slash) : "/";
parent.type = itDirectory;
mBrowser->AddOption(parent);
}
ItemList list;
Mpd->GetDirectory(dir, list);
sort(list.begin(), list.end(), CaseInsensitiveSorting());
for (ItemList::iterator it = list.begin(); it != list.end(); it++)
{
switch (it->type)
{
case itPlaylist:
{
mBrowser->AddOption(*it);
break;
}
case itDirectory:
{
if (it->name == subdir)
highlightme = mBrowser->Size();
mBrowser->AddOption(*it);
break;
}
case itSong:
{
bool bold = 0;
for (int i = 0; i < mPlaylist->Size(); i++)
{
if (mPlaylist->at(i).GetHash() == it->song->GetHash())
{
bold = 1;
break;
}
}
mBrowser->AddOption(*it, bold);
break;
}
}
}
mBrowser->Highlight(highlightme);
if (current_screen == csBrowser)
mBrowser->Hide();
}

View File

@@ -43,7 +43,6 @@ class CaseInsensitiveSorting
bool SortSongsByTrack(Song *, Song *);
void UpdateItemList(Menu<Item> *);
void UpdateSongList(Menu<Song> *);
bool Keypressed(int, const int *);
@@ -55,13 +54,11 @@ string IntoStr(mpd_TagItems);
string FindSharedDir(const string &, const string &);
string TotalPlaylistLength();
string DisplayStringPair(const StringPair &, void *, const Menu<StringPair> *);
string DisplayItem(const Item &, void *, const Menu<Item> *);
string DisplayColumns(string);
string DisplaySongInColumns(const Song &, void *, const Menu<Song> *);
string DisplaySong(const Song &, void * = &Config.song_list_format, const Menu<Song> * = NULL);
string GetInfo(Song &);
void ShowMessage(const string &, int = Config.message_delay_time);
void GetDirectory(string, string = "/");
#endif

View File

@@ -22,7 +22,14 @@
const string playlist_max_message = "playlist is at the max size";
MPDConnection::MPDConnection() : isConnected(0), itsErrorCode(0), itsMaxPlaylistLength(-1), MPD_HOST("localhost"), MPD_PORT(6600), MPD_TIMEOUT(15), itsUpdater(0), itsErrorHandler(0)
MPDConnection::MPDConnection() : isConnected(0),
itsErrorCode(0),
itsMaxPlaylistLength(-1),
MPD_HOST("localhost"),
MPD_PORT(6600),
MPD_TIMEOUT(15),
itsUpdater(0),
itsErrorHandler(0)
{
itsConnection = 0;
itsCurrentStats = 0;
@@ -467,7 +474,7 @@ int MPDConnection::AddSong(const string &path)
int MPDConnection::AddSong(const Song &s)
{
return !s.Empty() ? AddSong(s.GetFile()) : -1;
return !s.Empty() ? (s.IsFromDB() ? AddSong(s.GetFile()) : AddSong("file://" + s.GetFile())) : -1;
}
void MPDConnection::QueueAddSong(const string &path)

View File

@@ -82,6 +82,9 @@ class MPDConnection
bool Connected() const;
void Disconnect();
const string & GetHostname() { return MPD_HOST; }
int GetPort() { return MPD_PORT; }
void SetHostname(const string &);
void SetPort(int port) { MPD_PORT = port; }
void SetTimeout(int timeout) { MPD_TIMEOUT = timeout; }

View File

@@ -28,6 +28,7 @@
#include "mpdpp.h"
#include "ncmpcpp.h"
#include "browser.h"
#include "help.h"
#include "helpers.h"
#include "lyrics.h"
@@ -923,7 +924,7 @@ int main(int argc, char *argv[])
if (wCurrent == mBrowser && browsed_dir != "/")
{
mBrowser->Reset();
goto GO_TO_PARENT_DIR;
goto ENTER_BROWSER_SCREEN;
}
}
else if (Keypressed(input, Key.Enter))
@@ -938,7 +939,7 @@ int main(int argc, char *argv[])
}
case csBrowser:
{
GO_TO_PARENT_DIR:
ENTER_BROWSER_SCREEN:
const Item &item = mBrowser->Current();
switch (item.type)
@@ -1135,7 +1136,12 @@ int main(int argc, char *argv[])
mSearcher->Current().second = s;
}
else
mPlaylist->Current() = s;
{
if (wPrev == mPlaylist)
mPlaylist->Current() = s;
else if (wPrev == mBrowser)
*mBrowser->Current().song = s;
}
}
else
ShowMessage("Error writing tags!");
@@ -2025,7 +2031,8 @@ int main(int argc, char *argv[])
{
Mpd->DeletePlaylist(name);
ShowMessage("Playlist " + name + " deleted!");
GetDirectory("/");
if (!Config.local_browser)
GetDirectory("/");
}
else
ShowMessage("Aborted!");
@@ -2122,7 +2129,7 @@ int main(int argc, char *argv[])
UnlockStatusbar();
}
}
if (browsed_dir == "/" && !mBrowser->Empty())
if (!Config.local_browser && browsed_dir == "/" && !mBrowser->Empty())
GetDirectory(browsed_dir);
}
else if (Keypressed(input, Key.Stop))
@@ -2653,27 +2660,6 @@ int main(int argc, char *argv[])
ShowMessage("Cannot rename '" + full_old_dir + "' to '" + full_new_dir + "'!");
}
}
// blah, this key is already reserved for TagEditor. I'll merge this to its screen later.
/*else if (wCurrent == mBrowser && mBrowser->Current().type == itSong)
{
string old_name = mBrowser->Current().song->GetFile();
LockStatusbar();
wFooter->WriteXY(0, Config.statusbar_visibility, "[.b]Filename:[/b] ", 1);
string new_name = wFooter->GetString(old_name);
UnlockStatusbar();
if (!new_name.empty() && new_name != old_name)
{
string full_old_name = Config.mpd_music_dir + old_name;
string full_new_name = Config.mpd_music_dir + new_name;
if (rename(full_old_name.c_str(), full_new_name.c_str()) == 0)
{
Mpd->UpdateDirectory(FindSharedDir(old_name, new_name));
ShowMessage("'" + old_name + "' renamed to '" + new_name + "'");
}
else
ShowMessage("Cannot rename '" + old_name + "' to '" + new_name + "'!");
}
}*/
else if (wCurrent == mPlaylistList || (wCurrent == mBrowser && mBrowser->Current().type == itPlaylist))
{
string old_name = wCurrent == mPlaylistList ? mPlaylistList->GetOption() : mBrowser->Current().name;
@@ -2685,7 +2671,8 @@ int main(int argc, char *argv[])
{
Mpd->Rename(old_name, new_name);
ShowMessage("Playlist '" + old_name + "' renamed to '" + new_name + "'");
GetDirectory("/");
if (!Config.local_browser)
GetDirectory("/");
mPlaylistList->Clear(0);
}
}
@@ -2724,6 +2711,8 @@ int main(int argc, char *argv[])
if (s->GetDirectory() == EMPTY_TAG) // for streams
continue;
Config.local_browser = !s->IsFromDB();
string option = DisplaySong(*s, &Config.song_list_format, mPlaylist);
GetDirectory(s->GetDirectory());
for (int i = 0; i < mBrowser->Size(); i++)
@@ -2949,7 +2938,7 @@ int main(int argc, char *argv[])
if (id != mDialog->Size()-1)
{
// refresh playlist's lists
if (browsed_dir == "/")
if (!Config.local_browser && browsed_dir == "/")
GetDirectory("/");
mPlaylistList->Clear(0); // make playlist editor update itself
}
@@ -3097,31 +3086,43 @@ int main(int argc, char *argv[])
Config.ncmpc_like_songs_adding = !Config.ncmpc_like_songs_adding;
ShowMessage("Add mode: " + string(Config.ncmpc_like_songs_adding ? "Add item to playlist, remove if already added" : "Always add item to playlist"));
}
else if (Keypressed(input, Key.SwitchTagTypeList) && wCurrent == mLibArtists)
else if (Keypressed(input, Key.SwitchTagTypeList))
{
LockStatusbar();
wFooter->WriteXY(0, Config.statusbar_visibility, "Tag type ? [[.b]a[/b]rtist/[.b]y[/b]ear/[.b]g[/b]enre/[.b]c[/b]omposer/[.b]p[/b]erformer] ", 1);
int item;
curs_set(1);
do
if (wCurrent == mBrowser && Mpd->GetHostname()[0] == '/')
{
TraceMpdStatus();
wFooter->ReadKey(item);
Config.local_browser = !Config.local_browser;
ShowMessage("Browse mode: " + string(Config.local_browser ? "Local filesystem" : "MPD music dir"));
browsed_dir = Config.local_browser ? home_folder : "/";
mBrowser->Reset();
GetDirectory(browsed_dir);
redraw_header = 1;
}
while (item != 'a' && item != 'y' && item != 'g' && item != 'c' && item != 'p');
curs_set(0);
UnlockStatusbar();
mpd_TagItems new_tagitem = IntoTagItem(item);
if (new_tagitem != Config.media_lib_primary_tag)
else if (wCurrent == mLibArtists)
{
Config.media_lib_primary_tag = new_tagitem;
string item_type = IntoStr(Config.media_lib_primary_tag);
mLibArtists->SetTitle(item_type + "s");
mLibArtists->Reset();
mLibArtists->Clear(0);
mLibArtists->Display();
ToLower(item_type);
ShowMessage("Switched to list of " + item_type + " tag");
LockStatusbar();
wFooter->WriteXY(0, Config.statusbar_visibility, "Tag type ? [[.b]a[/b]rtist/[.b]y[/b]ear/[.b]g[/b]enre/[.b]c[/b]omposer/[.b]p[/b]erformer] ", 1);
int item;
curs_set(1);
do
{
TraceMpdStatus();
wFooter->ReadKey(item);
}
while (item != 'a' && item != 'y' && item != 'g' && item != 'c' && item != 'p');
curs_set(0);
UnlockStatusbar();
mpd_TagItems new_tagitem = IntoTagItem(item);
if (new_tagitem != Config.media_lib_primary_tag)
{
Config.media_lib_primary_tag = new_tagitem;
string item_type = IntoStr(Config.media_lib_primary_tag);
mLibArtists->SetTitle(item_type + "s");
mLibArtists->Reset();
mLibArtists->Clear(0);
mLibArtists->Display();
ToLower(item_type);
ShowMessage("Switched to list of " + item_type + " tag");
}
}
}
else if (Keypressed(input, Key.SongInfo))

View File

@@ -202,6 +202,7 @@ void DefaultConfiguration(ncmpcpp_config &conf)
conf.albums_in_tag_editor = false;
conf.incremental_seeking = true;
conf.now_playing_lyrics = false;
conf.local_browser = false;
conf.set_window_title = true;
conf.mpd_connection_timeout = 15;
conf.crossfade_time = 5;

View File

@@ -142,6 +142,7 @@ struct ncmpcpp_config
bool albums_in_tag_editor;
bool incremental_seeking;
bool now_playing_lyrics;
bool local_browser;
int mpd_connection_timeout;
int crossfade_time;

View File

@@ -61,11 +61,11 @@ Song::Song(mpd_Song *s, bool copy_ptr) : itsSong(s),
isStream = 1;
// generate pseudo-hash
for (int i = 0; i < strlen(itsSong->file); i++)
for (int i = 0; i < itsFile.length(); i++)
{
itsHash += itsSong->file[i];
if (i%2)
itsHash *= itsSong->file[i];
itsHash += itsFile[i];
if (i%3)
itsHash *= itsFile[i];
}
}

View File

@@ -18,6 +18,7 @@
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "browser.h"
#include "helpers.h"
#include "search_engine.h"
#include "settings.h"

View File

@@ -129,6 +129,21 @@ string DisplayTag(const Song &s, void *data, const Menu<Song> *)
}
}
void ReadTagsFromFile(mpd_Song *s)
{
TagLib::FileRef f(s->file);
if (f.isNull())
return;
s->artist = !f.tag()->artist().isEmpty() ? str_pool_get(f.tag()->artist().to8Bit(UNICODE).c_str()) : 0;
s->title = !f.tag()->title().isEmpty() ? str_pool_get(f.tag()->title().to8Bit(UNICODE).c_str()) : 0;
s->album = !f.tag()->album().isEmpty() ? str_pool_get(f.tag()->album().to8Bit(UNICODE).c_str()) : 0;
s->track = f.tag()->track() ? str_pool_get(IntoStr(f.tag()->track()).c_str()) : 0;
s->date = f.tag()->year() ? str_pool_get(IntoStr(f.tag()->year()).c_str()) : 0;
s->genre = !f.tag()->genre().isEmpty() ? str_pool_get(f.tag()->genre().to8Bit(UNICODE).c_str()) : 0;
s->comment = !f.tag()->comment().isEmpty() ? str_pool_get(f.tag()->comment().to8Bit(UNICODE).c_str()) : 0;
s->time = f.audioProperties()->length();
}
bool GetSongTags(Song &s)
{
string path_to_file;

View File

@@ -40,6 +40,7 @@ string DisplayTag(const Song &, void *, const Menu<Song> *);
SongSetFunction IntoSetFunction(mpd_TagItems);
void ReadTagsFromFile(mpd_Song *);
bool GetSongTags(Song &);
bool WriteTags(Song &);