diff --git a/Makefile.am b/Makefile.am
index 11e9d4476..64e4b1d4e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1932,6 +1932,9 @@ test_DumpDatabase_SOURCES = test/DumpDatabase.cxx \
src/db/Selection.cxx \
src/db/PlaylistVector.cxx \
src/db/DatabaseLock.cxx \
+ src/AudioFormat.cxx \
+ src/AudioParser.cxx \
+ src/pcm/SampleFormat.cxx \
src/SongSave.cxx \
src/DetachedSong.cxx \
src/TagSave.cxx \
diff --git a/NEWS b/NEWS
index 105f3375c..817824d2e 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,8 @@ ver 0.21 (not yet released)
- "outputs" prints the plugin name
- "outputset" sets runtime attributes
- close connection when client sends HTTP request
+* database
+ - simple: scan audio formats
* player
- "one-shot" single mode
* input
diff --git a/doc/protocol.xml b/doc/protocol.xml
index e0532e0e4..c2a74af1e 100644
--- a/doc/protocol.xml
+++ b/doc/protocol.xml
@@ -256,6 +256,17 @@
+
+
+ Format: the audio format of the song
+ (or an approximation to a format supported by MPD and the
+ decoder plugin being used). When playing this file, the
+ audio value in the status
+ response should be the same.
+
+
+
Last-Modified: the time stamp of the
diff --git a/src/SongPrint.cxx b/src/SongPrint.cxx
index 9db113f80..b42287c02 100644
--- a/src/SongPrint.cxx
+++ b/src/SongPrint.cxx
@@ -92,6 +92,9 @@ song_print_info(Response &r, const LightSong &song, bool base) noexcept
if (!IsNegative(song.mtime))
time_print(r, "Last-Modified", song.mtime);
+ if (song.audio_format.IsDefined())
+ r.Format("Format: %s\n", ToString(song.audio_format).c_str());
+
tag_print(r, song.tag);
}
diff --git a/src/SongSave.cxx b/src/SongSave.cxx
index c3593d823..e42c58f49 100644
--- a/src/SongSave.cxx
+++ b/src/SongSave.cxx
@@ -19,6 +19,7 @@
#include "config.h"
#include "SongSave.hxx"
+#include "AudioParser.hxx"
#include "db/plugins/simple/Song.hxx"
#include "DetachedSong.hxx"
#include "TagSave.hxx"
@@ -28,6 +29,8 @@
#include "tag/Tag.hxx"
#include "tag/Builder.hxx"
#include "util/ChronoUtil.hxx"
+#include "util/StringAPI.hxx"
+#include "util/StringBuffer.hxx"
#include "util/StringStrip.hxx"
#include "util/RuntimeError.hxx"
#include "util/NumberParser.hxx"
@@ -56,6 +59,9 @@ song_save(BufferedOutputStream &os, const Song &song)
tag_save(os, song.tag);
+ if (song.audio_format.IsDefined())
+ os.Format("Format: %s\n", ToString(song.audio_format).c_str());
+
if (!IsNegative(song.mtime))
os.Format(SONG_MTIME ": %li\n",
(long)std::chrono::system_clock::to_time_t(song.mtime));
@@ -78,7 +84,8 @@ song_save(BufferedOutputStream &os, const DetachedSong &song)
}
std::unique_ptr
-song_load(TextFile &file, const char *uri)
+song_load(TextFile &file, const char *uri,
+ AudioFormat *audio_format_r)
{
auto song = std::make_unique(uri);
@@ -100,6 +107,15 @@ song_load(TextFile &file, const char *uri)
tag.AddItem(type, value);
} else if (strcmp(line, "Time") == 0) {
tag.SetDuration(SignedSongTime::FromS(ParseDouble(value)));
+ } else if (StringIsEqual(line, "Format")) {
+ if (audio_format_r != nullptr) {
+ try {
+ *audio_format_r =
+ ParseAudioFormat(value, false);
+ } catch (...) {
+ /* ignore parser errors */
+ }
+ }
} else if (strcmp(line, "Playlist") == 0) {
tag.SetHasPlaylist(strcmp(value, "yes") == 0);
} else if (strcmp(line, SONG_MTIME) == 0) {
diff --git a/src/SongSave.hxx b/src/SongSave.hxx
index 9e6ae151d..81505b91c 100644
--- a/src/SongSave.hxx
+++ b/src/SongSave.hxx
@@ -25,6 +25,7 @@
#define SONG_BEGIN "song_begin: "
struct Song;
+struct AudioFormat;
class DetachedSong;
class BufferedOutputStream;
class TextFile;
@@ -42,6 +43,7 @@ song_save(BufferedOutputStream &os, const DetachedSong &song);
* Throws #std::runtime_error on error.
*/
std::unique_ptr
-song_load(TextFile &file, const char *uri);
+song_load(TextFile &file, const char *uri,
+ AudioFormat *audio_format_r=nullptr);
#endif
diff --git a/src/SongUpdate.cxx b/src/SongUpdate.cxx
index e43c45961..5738955b3 100644
--- a/src/SongUpdate.cxx
+++ b/src/SongUpdate.cxx
@@ -77,19 +77,23 @@ Song::UpdateFile(Storage &storage) noexcept
return false;
TagBuilder tag_builder;
+ auto new_audio_format = AudioFormat::Undefined();
const auto path_fs = storage.MapFS(relative_uri.c_str());
if (path_fs.IsNull()) {
const auto absolute_uri =
storage.MapUTF8(relative_uri.c_str());
- if (!tag_stream_scan(absolute_uri.c_str(), tag_builder))
+ if (!tag_stream_scan(absolute_uri.c_str(), tag_builder,
+ &new_audio_format))
return false;
} else {
- if (!ScanFileTagsWithGeneric(path_fs, tag_builder))
+ if (!ScanFileTagsWithGeneric(path_fs, tag_builder,
+ &new_audio_format))
return false;
}
mtime = info.mtime;
+ audio_format = new_audio_format;
tag_builder.Commit(tag);
return true;
}
diff --git a/src/db/LightSong.hxx b/src/db/LightSong.hxx
index 7b5b51043..7bc21e030 100644
--- a/src/db/LightSong.hxx
+++ b/src/db/LightSong.hxx
@@ -21,6 +21,7 @@
#define MPD_LIGHT_SONG_HXX
#include "Chrono.hxx"
+#include "AudioFormat.hxx"
#include "Compiler.h"
#include
@@ -78,6 +79,12 @@ struct LightSong {
*/
SongTime end_time = SongTime::zero();
+ /**
+ * The audio format of the song, if given by the decoder
+ * plugin. May be undefined if unknown.
+ */
+ AudioFormat audio_format = AudioFormat::Undefined();
+
LightSong(const char *_uri, const Tag &_tag) noexcept
:uri(_uri), tag(_tag) {}
diff --git a/src/db/plugins/simple/DirectorySave.cxx b/src/db/plugins/simple/DirectorySave.cxx
index 3cba77295..35406cadd 100644
--- a/src/db/plugins/simple/DirectorySave.cxx
+++ b/src/db/plugins/simple/DirectorySave.cxx
@@ -161,10 +161,15 @@ directory_load(TextFile &file, Directory &directory)
if (directory.FindSong(name) != nullptr)
throw FormatRuntimeError("Duplicate song '%s'", name);
- auto song = song_load(file, name);
+ auto audio_format = AudioFormat::Undefined();
+ auto detached_song = song_load(file, name,
+ &audio_format);
- directory.AddSong(Song::NewFrom(std::move(*song),
- directory));
+ auto song = Song::NewFrom(std::move(*detached_song),
+ directory);
+ song->audio_format = audio_format;
+
+ directory.AddSong(song);
} else if ((p = StringAfterPrefix(line, PLAYLIST_META_BEGIN))) {
const char *name = p;
playlist_metadata_load(file, directory.playlists, name);
diff --git a/src/db/plugins/simple/Song.cxx b/src/db/plugins/simple/Song.cxx
index 61ed929df..4933e0e38 100644
--- a/src/db/plugins/simple/Song.cxx
+++ b/src/db/plugins/simple/Song.cxx
@@ -104,5 +104,6 @@ Song::Export() const noexcept
dest.mtime = mtime;
dest.start_time = start_time;
dest.end_time = end_time;
+ dest.audio_format = audio_format;
return dest;
}
diff --git a/src/db/plugins/simple/Song.hxx b/src/db/plugins/simple/Song.hxx
index 6802c7059..1cd344f9f 100644
--- a/src/db/plugins/simple/Song.hxx
+++ b/src/db/plugins/simple/Song.hxx
@@ -23,6 +23,7 @@
#include "check.h"
#include "Chrono.hxx"
#include "tag/Tag.hxx"
+#include "AudioFormat.hxx"
#include "Compiler.h"
#include
@@ -88,6 +89,12 @@ struct Song {
*/
SongTime end_time = SongTime::zero();
+ /**
+ * The audio format of the song, if given by the decoder
+ * plugin. May be undefined if unknown.
+ */
+ AudioFormat audio_format = AudioFormat::Undefined();
+
/**
* The file name.
*/