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

View File

@ -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);
} }

View File

@ -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.

View File

@ -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();

View File

@ -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());
} }

View File

@ -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) {

View File

@ -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);
} }

View File

@ -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");

View File

@ -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;
} }

View File

@ -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) {

View File

@ -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);
} }

View File

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

View File

@ -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);

View File

@ -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;
} }

View File

@ -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();
} }

View File

@ -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)

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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();
} }

View File

@ -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)
{ {

View File

@ -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();
} }
/** /**

View File

@ -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;

View File

@ -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) {

View File

@ -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

View File

@ -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('|');