Tag: use SignedSongTime for the song duration

This commit is contained in:
Max Kellermann 2014-08-29 12:14:27 +02:00
parent 8ce30c6a69
commit 7c25d83f1c
25 changed files with 103 additions and 84 deletions

@ -58,11 +58,16 @@ DetachedSong::IsInDatabase() const
return !uri_has_scheme(_uri) && !PathTraitsUTF8::IsAbsolute(_uri); return !uri_has_scheme(_uri) && !PathTraitsUTF8::IsAbsolute(_uri);
} }
double SignedSongTime
DetachedSong::GetDuration() const DetachedSong::GetDuration() const
{ {
if (end_time.IsPositive()) SongTime a = start_time, b = end_time;
return (end_time - start_time).ToDoubleS(); if (!b.IsPositive()) {
if (tag.duration.IsNegative())
return tag.duration;
return tag.time - start_time.ToDoubleS(); b = SongTime(tag.duration);
}
return SignedSongTime(b - a);
} }

@ -213,7 +213,7 @@ public:
} }
gcc_pure gcc_pure
double GetDuration() const; SignedSongTime GetDuration() const;
/** /**
* Update the #tag and #mtime. * Update the #tag and #mtime.

@ -349,7 +349,7 @@ Player::WaitForDecoder()
decoder_starting = true; decoder_starting = true;
/* update PlayerControl's song information */ /* update PlayerControl's song information */
pc.total_time = pc.next_song->GetDuration(); pc.total_time = pc.next_song->GetDuration().ToDoubleS();
pc.bit_rate = 0; pc.bit_rate = 0;
pc.audio_format.Clear(); pc.audio_format.Clear();
@ -374,7 +374,7 @@ real_song_duration(const DetachedSong &song, double decoder_duration)
if (decoder_duration <= 0.0) if (decoder_duration <= 0.0)
/* the decoder plugin didn't provide information; fall /* the decoder plugin didn't provide information; fall
back to Song::GetDuration() */ back to Song::GetDuration() */
return song.GetDuration(); return song.GetDuration().ToDoubleS();
const SongTime start_time = song.GetStartTime(); const SongTime start_time = song.GetStartTime();
const SongTime end_time = song.GetEndTime(); const SongTime end_time = song.GetEndTime();

@ -119,7 +119,7 @@ song_print_info(Client &client, const DetachedSong &song, bool base)
tag_print_values(client, song.GetTag()); tag_print_values(client, song.GetTag());
double duration = song.GetDuration(); const auto duration = song.GetDuration();
if (duration >= 0) if (!duration.IsNegative())
client_printf(client, "Time: %u\n", unsigned(duration + 0.5)); client_printf(client, "Time: %u\n", duration.RoundS());
} }

@ -100,7 +100,7 @@ song_load(TextFile &file, const char *uri,
if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) { if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) {
tag.AddItem(type, value); tag.AddItem(type, value);
} else if (strcmp(line, "Time") == 0) { } else if (strcmp(line, "Time") == 0) {
tag.SetTime(atoi(value)); tag.SetDuration(SignedSongTime::FromS(atof(value)));
} else if (strcmp(line, "Playlist") == 0) { } else if (strcmp(line, "Playlist") == 0) {
tag.SetHasPlaylist(strcmp(value, "yes") == 0); tag.SetHasPlaylist(strcmp(value, "yes") == 0);
} else if (strcmp(line, SONG_MTIME) == 0) { } else if (strcmp(line, SONG_MTIME) == 0) {

@ -52,8 +52,8 @@ tag_print_values(Client &client, const Tag &tag)
void tag_print(Client &client, const Tag &tag) void tag_print(Client &client, const Tag &tag)
{ {
if (tag.time >= 0) if (!tag.duration.IsNegative())
client_printf(client, SONG_TIME "%i\n", tag.time); client_printf(client, SONG_TIME "%i\n", tag.duration.RoundS());
tag_print_values(client, tag); tag_print_values(client, tag);
} }

@ -27,8 +27,8 @@
void void
tag_save(BufferedOutputStream &os, const Tag &tag) tag_save(BufferedOutputStream &os, const Tag &tag)
{ {
if (tag.time >= 0) if (!tag.duration.IsNegative())
os.Format(SONG_TIME "%i\n", tag.time); os.Format(SONG_TIME "%f\n", tag.duration.ToDoubleS());
if (tag.has_playlist) if (tag.has_playlist)
os.Format("Playlist: yes\n"); os.Format("Playlist: yes\n");

@ -64,7 +64,10 @@ static bool
stats_visitor_song(SearchStats &stats, const LightSong &song) stats_visitor_song(SearchStats &stats, const LightSong &song)
{ {
stats.n_songs++; stats.n_songs++;
stats.total_time_s += song.GetDuration();
const auto duration = song.GetDuration();
if (!duration.IsNegative())
stats.total_time_s += duration.ToS();
return true; return true;
} }
@ -79,8 +82,8 @@ CollectGroupCounts(TagCountMap &map, TagType group, const Tag &tag)
SearchStats())); SearchStats()));
SearchStats &s = r.first->second; SearchStats &s = r.first->second;
++s.n_songs; ++s.n_songs;
if (tag.time > 0) if (!tag.duration.IsNegative())
s.total_time_s += tag.time; s.total_time_s += tag.duration.ToS();
found = true; found = true;
} }

@ -40,8 +40,8 @@ static void
StatsVisitTag(DatabaseStats &stats, StringSet &artists, StringSet &albums, StatsVisitTag(DatabaseStats &stats, StringSet &artists, StringSet &albums,
const Tag &tag) const Tag &tag)
{ {
if (tag.time > 0) if (!tag.duration.IsNegative())
stats.total_duration += tag.time; stats.total_duration += tag.duration.ToS();
for (const auto &item : tag) { for (const auto &item : tag) {
switch (item.type) { switch (item.type) {

@ -20,14 +20,16 @@
#include "LightSong.hxx" #include "LightSong.hxx"
#include "tag/Tag.hxx" #include "tag/Tag.hxx"
double SignedSongTime
LightSong::GetDuration() const LightSong::GetDuration() const
{ {
if (end_time.IsPositive()) SongTime a = start_time, b = end_time;
return (end_time - start_time).ToDoubleS(); if (!b.IsPositive()) {
if (tag->duration.IsNegative())
return tag->duration;
if (tag->time <= 0) b = SongTime(tag->duration);
return 0; }
return tag->time - start_time.ToDoubleS(); return SignedSongTime(b - a);
} }

@ -87,7 +87,7 @@ struct LightSong {
} }
gcc_pure gcc_pure
double GetDuration() const; SignedSongTime GetDuration() const;
}; };
#endif #endif

@ -199,7 +199,10 @@ ProxySong::ProxySong(const mpd_song *song)
#endif #endif
TagBuilder tag_builder; TagBuilder tag_builder;
tag_builder.SetTime(mpd_song_get_duration(song));
const unsigned duration = mpd_song_get_duration(song);
if (duration > 0)
tag_builder.SetDuration(SignedSongTime::FromS(duration));
for (const auto *i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i) for (const auto *i = &tag_table[0]; i->d != TAG_NUM_OF_ITEM_TYPES; ++i)
Copy(tag_builder, i->d, song, i->s); Copy(tag_builder, i->d, song, i->s);

@ -59,28 +59,28 @@ ParseItemClass(const char *name, size_t length)
} }
gcc_pure gcc_pure
static int static SignedSongTime
ParseDuration(const char *duration) ParseDuration(const char *duration)
{ {
char *endptr; char *endptr;
unsigned result = ParseUnsigned(duration, &endptr); unsigned result = ParseUnsigned(duration, &endptr);
if (endptr == duration || *endptr != ':') if (endptr == duration || *endptr != ':')
return 0; return SignedSongTime::Negative();
result *= 60; result *= 60;
duration = endptr + 1; duration = endptr + 1;
result += ParseUnsigned(duration, &endptr); result += ParseUnsigned(duration, &endptr);
if (endptr == duration || *endptr != ':') if (endptr == duration || *endptr != ':')
return 0; return SignedSongTime::Negative();
result *= 60; result *= 60;
duration = endptr + 1; duration = endptr + 1;
result += ParseUnsigned(duration, &endptr); result += ParseUnsigned(duration, &endptr);
if (endptr == duration || *endptr != 0) if (endptr == duration || *endptr != 0)
return 0; return SignedSongTime::Negative();
return result; return SignedSongTime::FromS(result);
} }
/** /**
@ -183,7 +183,7 @@ protected:
const char *duration = const char *duration =
GetAttribute(attrs, "duration"); GetAttribute(attrs, "duration");
if (duration != nullptr) if (duration != nullptr)
tag.SetTime(ParseDuration(duration)); tag.SetDuration(ParseDuration(duration));
state = RES; state = RES;
} }

@ -105,7 +105,7 @@ mpd_despotify_tag_from_track(const ds_track &track)
tag.AddItem(TAG_ALBUM, track.album); tag.AddItem(TAG_ALBUM, track.album);
tag.AddItem(TAG_DATE, date); tag.AddItem(TAG_DATE, date);
tag.AddItem(TAG_COMMENT, comment); tag.AddItem(TAG_COMMENT, comment);
tag.SetTime(track.length / 1000); tag.SetDuration(SignedSongTime::FromMS(track.length));
return tag.Commit(); return tag.Commit();
} }

@ -363,16 +363,20 @@ RoarOutput::SendTag(const Tag &tag)
const ScopeLock protect(mutex); const ScopeLock protect(mutex);
size_t cnt = 1; size_t cnt = 0;
struct roar_keyval vals[32]; struct roar_keyval vals[32];
char uuid_buf[32][64]; char uuid_buf[32][64];
char timebuf[16]; char timebuf[16];
snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d", if (!tag.duration.IsNegative()) {
tag.time / 3600, (tag.time % 3600) / 60, tag.time % 60); const unsigned seconds = tag.duration.ToS();
snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d",
seconds / 3600, (seconds % 3600) / 60, seconds % 60);
vals[0].key = const_cast<char *>("LENGTH"); vals[cnt].key = const_cast<char *>("LENGTH");
vals[0].value = timebuf; vals[cnt].value = timebuf;
++cnt;
}
for (const auto &item : tag) { for (const auto &item : tag) {
if (cnt >= 32) if (cnt >= 32)

@ -89,7 +89,7 @@ extm3u_parse_tag(const char *line)
return Tag(); return Tag();
TagBuilder tag; TagBuilder tag;
tag.SetTime(duration); tag.SetDuration(SignedSongTime::FromS(unsigned(duration)));
/* unfortunately, there is no real specification for the /* unfortunately, there is no real specification for the
EXTM3U format, so we must assume that the string after the EXTM3U format, so we must assume that the string after the

@ -85,7 +85,7 @@ pls_parser(GKeyFile *keyfile, std::forward_list<DetachedSong> &songs)
int length = g_key_file_get_integer(keyfile, "playlist", key, int length = g_key_file_get_integer(keyfile, "playlist", key,
nullptr); nullptr);
if (length > 0) if (length > 0)
tag.SetTime(length); tag.SetDuration(SignedSongTime::FromS(length));
songs.emplace_front(uri, tag.Commit()); songs.emplace_front(uri, tag.Commit());
g_free(uri); g_free(uri);

@ -215,7 +215,7 @@ handle_end_map(void *ctx)
soundcloud_config.apikey.c_str(), nullptr); soundcloud_config.apikey.c_str(), nullptr);
TagBuilder tag; TagBuilder tag;
tag.SetTime(data->duration / 1000); tag.SetDuration(SignedSongTime::FromMS(data->duration));
if (data->title != nullptr) if (data->title != nullptr)
tag.AddItem(TAG_NAME, data->title); tag.AddItem(TAG_NAME, data->title);

@ -463,18 +463,19 @@ playlist::SetSongIdRange(PlayerControl &pc, unsigned id,
} }
DetachedSong &song = queue.Get(position); DetachedSong &song = queue.Get(position);
if (song.GetTag().time > 0) {
const auto duration = song.GetTag().duration;
if (!duration.IsNegative()) {
/* validate the offsets */ /* validate the offsets */
const unsigned duration = song.GetTag().time; if (start > duration) {
if (start.ToMS() / 1000u > duration) {
error.Set(playlist_domain, error.Set(playlist_domain,
int(PlaylistResult::BAD_RANGE), int(PlaylistResult::BAD_RANGE),
"Invalid start offset"); "Invalid start offset");
return false; return false;
} }
if (end.ToMS() / 1000u > duration) if (end >= duration)
end = SongTime::zero(); end = SongTime::zero();
} }

@ -61,7 +61,7 @@ tag_name_parse_i(const char *name)
void void
Tag::Clear() Tag::Clear()
{ {
time = -1; duration = SignedSongTime::Negative();
has_playlist = false; has_playlist = false;
tag_pool_lock.lock(); tag_pool_lock.lock();
@ -75,7 +75,7 @@ Tag::Clear()
} }
Tag::Tag(const Tag &other) Tag::Tag(const Tag &other)
:time(other.time), has_playlist(other.has_playlist), :duration(other.duration), has_playlist(other.has_playlist),
num_items(other.num_items), num_items(other.num_items),
items(nullptr) items(nullptr)
{ {

@ -22,6 +22,7 @@
#include "TagType.h" // IWYU pragma: export #include "TagType.h" // IWYU pragma: export
#include "TagItem.hxx" // IWYU pragma: export #include "TagItem.hxx" // IWYU pragma: export
#include "Chrono.hxx"
#include "Compiler.h" #include "Compiler.h"
#include <algorithm> #include <algorithm>
@ -35,12 +36,10 @@
*/ */
struct Tag { struct Tag {
/** /**
* The duration of the song (in seconds). A value of zero * The duration of the song. A negative value means that the
* means that the length is unknown. If the duration is * length is unknown.
* really between zero and one second, you should round up to
* 1.
*/ */
int time; SignedSongTime duration;
/** /**
* Does this file have an embedded playlist (e.g. embedded CUE * Does this file have an embedded playlist (e.g. embedded CUE
@ -57,13 +56,13 @@ struct Tag {
/** /**
* Create an empty tag. * Create an empty tag.
*/ */
Tag():time(-1), has_playlist(false), Tag():duration(SignedSongTime::Negative()), has_playlist(false),
num_items(0), items(nullptr) {} num_items(0), items(nullptr) {}
Tag(const Tag &other); Tag(const Tag &other);
Tag(Tag &&other) Tag(Tag &&other)
:time(other.time), has_playlist(other.has_playlist), :duration(other.duration), has_playlist(other.has_playlist),
num_items(other.num_items), items(other.items) { num_items(other.num_items), items(other.items) {
other.items = nullptr; other.items = nullptr;
other.num_items = 0; other.num_items = 0;
@ -79,7 +78,7 @@ struct Tag {
Tag &operator=(const Tag &other) = delete; Tag &operator=(const Tag &other) = delete;
Tag &operator=(Tag &&other) { Tag &operator=(Tag &&other) {
time = other.time; duration = other.duration;
has_playlist = other.has_playlist; has_playlist = other.has_playlist;
std::swap(items, other.items); std::swap(items, other.items);
std::swap(num_items, other.num_items); std::swap(num_items, other.num_items);
@ -87,8 +86,8 @@ struct Tag {
} }
/** /**
* Returns true if the tag contains no items. This ignores the "time" * Returns true if the tag contains no items. This ignores
* attribute. * the "duration" attribute.
*/ */
bool IsEmpty() const { bool IsEmpty() const {
return num_items == 0; return num_items == 0;
@ -98,7 +97,7 @@ struct Tag {
* Returns true if the tag contains any information. * Returns true if the tag contains any information.
*/ */
bool IsDefined() const { bool IsDefined() const {
return !IsEmpty() || time >= 0; return !IsEmpty() || !duration.IsNegative();
} }
/** /**

@ -29,7 +29,7 @@
#include <stdlib.h> #include <stdlib.h>
TagBuilder::TagBuilder(const Tag &other) TagBuilder::TagBuilder(const Tag &other)
:time(other.time), has_playlist(other.has_playlist) :duration(other.duration), has_playlist(other.has_playlist)
{ {
items.reserve(other.num_items); items.reserve(other.num_items);
@ -40,7 +40,7 @@ TagBuilder::TagBuilder(const Tag &other)
} }
TagBuilder::TagBuilder(Tag &&other) TagBuilder::TagBuilder(Tag &&other)
:time(other.time), has_playlist(other.has_playlist) :duration(other.duration), has_playlist(other.has_playlist)
{ {
/* move all TagItem pointers from the Tag object; we don't /* move all TagItem pointers from the Tag object; we don't
need to contact the tag pool, because all we do is move need to contact the tag pool, because all we do is move
@ -58,7 +58,7 @@ TagBuilder &
TagBuilder::operator=(const TagBuilder &other) TagBuilder::operator=(const TagBuilder &other)
{ {
/* copy all attributes */ /* copy all attributes */
time = other.time; duration = other.duration;
has_playlist = other.has_playlist; has_playlist = other.has_playlist;
items = other.items; items = other.items;
@ -74,7 +74,7 @@ TagBuilder::operator=(const TagBuilder &other)
TagBuilder & TagBuilder &
TagBuilder::operator=(TagBuilder &&other) TagBuilder::operator=(TagBuilder &&other)
{ {
time = other.time; duration = other.duration;
has_playlist = other.has_playlist; has_playlist = other.has_playlist;
items = std::move(other.items); items = std::move(other.items);
@ -84,7 +84,7 @@ TagBuilder::operator=(TagBuilder &&other)
TagBuilder & TagBuilder &
TagBuilder::operator=(Tag &&other) TagBuilder::operator=(Tag &&other)
{ {
time = other.time; duration = other.duration;
has_playlist = other.has_playlist; has_playlist = other.has_playlist;
/* move all TagItem pointers from the Tag object; we don't /* move all TagItem pointers from the Tag object; we don't
@ -105,7 +105,7 @@ TagBuilder::operator=(Tag &&other)
void void
TagBuilder::Clear() TagBuilder::Clear()
{ {
time = -1; duration = SignedSongTime::Negative();
has_playlist = false; has_playlist = false;
RemoveAll(); RemoveAll();
} }
@ -115,7 +115,7 @@ TagBuilder::Commit(Tag &tag)
{ {
tag.Clear(); tag.Clear();
tag.time = time; tag.duration = duration;
tag.has_playlist = has_playlist; tag.has_playlist = has_playlist;
/* move all TagItem pointers to the new Tag object without /* move all TagItem pointers to the new Tag object without
@ -162,8 +162,8 @@ TagBuilder::HasType(TagType type) const
void void
TagBuilder::Complement(const Tag &other) TagBuilder::Complement(const Tag &other)
{ {
if (time <= 0) if (duration.IsNegative())
time = other.time; duration = other.duration;
has_playlist |= other.has_playlist; has_playlist |= other.has_playlist;

@ -21,6 +21,7 @@
#define MPD_TAG_BUILDER_HXX #define MPD_TAG_BUILDER_HXX
#include "TagType.h" #include "TagType.h"
#include "Chrono.hxx"
#include "Compiler.h" #include "Compiler.h"
#include <vector> #include <vector>
@ -35,12 +36,10 @@ struct Tag;
*/ */
class TagBuilder { class TagBuilder {
/** /**
* The duration of the song (in seconds). A value of zero * The duration of the song. A negative value means that the
* means that the length is unknown. If the duration is * length is unknown.
* really between zero and one second, you should round up to
* 1.
*/ */
int time; SignedSongTime duration;
/** /**
* Does this file have an embedded playlist (e.g. embedded CUE * Does this file have an embedded playlist (e.g. embedded CUE
@ -56,7 +55,7 @@ public:
* Create an empty tag. * Create an empty tag.
*/ */
TagBuilder() TagBuilder()
:time(-1), has_playlist(false) {} :duration(SignedSongTime::Negative()), has_playlist(false) {}
~TagBuilder() { ~TagBuilder() {
Clear(); Clear();
@ -73,8 +72,8 @@ public:
TagBuilder &operator=(Tag &&other); TagBuilder &operator=(Tag &&other);
/** /**
* Returns true if the tag contains no items. This ignores the "time" * Returns true if the tag contains no items. This ignores
* attribute. * the "duration" attribute.
*/ */
bool IsEmpty() const { bool IsEmpty() const {
return items.empty(); return items.empty();
@ -85,7 +84,7 @@ public:
*/ */
gcc_pure gcc_pure
bool IsDefined() const { bool IsDefined() const {
return time >= 0 || has_playlist || !IsEmpty(); return !duration.IsNegative() || has_playlist || !IsEmpty();
} }
void Clear(); void Clear();
@ -109,8 +108,8 @@ public:
*/ */
Tag *CommitNew(); Tag *CommitNew();
void SetTime(int _time) { void SetDuration(SignedSongTime _duration) {
time = _time; duration = _duration;
} }
void SetHasPlaylist(bool _has_playlist) { void SetHasPlaylist(bool _has_playlist) {

@ -27,7 +27,7 @@ add_tag_duration(unsigned seconds, void *ctx)
{ {
TagBuilder &tag = *(TagBuilder *)ctx; TagBuilder &tag = *(TagBuilder *)ctx;
tag.SetTime(seconds); tag.SetDuration(SignedSongTime::FromS(seconds));
} }
static void static void

@ -155,10 +155,13 @@ Client::AllowFile(gcc_unused Path path_fs, gcc_unused Error &error) const
static std::string static std::string
ToString(const Tag &tag) ToString(const Tag &tag)
{ {
char buffer[64]; std::string result;
sprintf(buffer, "%d", tag.time);
std::string result = buffer; if (!tag.duration.IsNegative()) {
char buffer[64];
sprintf(buffer, "%d", tag.duration.ToMS());
result.append(buffer);
}
for (const auto &item : tag) { for (const auto &item : tag) {
result.push_back('|'); result.push_back('|');