diff --git a/TODO b/TODO index 39fd7a31..a67fcfcd 100644 --- a/TODO +++ b/TODO @@ -43,11 +43,9 @@ - Search patterns in the genre list - ReleaseView/ArtistView/GenreView: -> Track count for this special category. Easier: add a special entry '' that contains everything? - MediaPlayer: move slider using js (http://redmine.webtoolkit.eu/boards/2/topics/7924?r=8478) - - TrackView : handle original release date - TrackView : handle duration > 1 hour - TrackView : select only relevant columns to speed up queries (do not fetch eveything) - TrackView : Reselect the current selected item when displaying the updated search results - - ReleaseView: display the original release's publication year - OGG metadata -> properly handle metadata nested in the audio stream - Filters: use directly the id as constraint, instead of the name? - Implement a playlist: diff --git a/src/database-updater/DatabaseUpdater.cpp b/src/database-updater/DatabaseUpdater.cpp index aa50da09..97c1a9be 100644 --- a/src/database-updater/DatabaseUpdater.cpp +++ b/src/database-updater/DatabaseUpdater.cpp @@ -283,8 +283,8 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) // We estimate this is a audio file if: // - we found a least one audio stream // - the duration is not null - if (items.find(MetaData::AudioStreams) == items.end() - || boost::any_cast >(items[MetaData::AudioStreams]).empty()) + if (items.find(MetaData::Type::AudioStreams) == items.end() + || boost::any_cast >(items[MetaData::Type::AudioStreams]).empty()) { LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Skipped '" << file << "' (no audio stream found)"; @@ -295,8 +295,8 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) } return; } - if (items.find(MetaData::Duration) == items.end() - || boost::any_cast(items[MetaData::Duration]).total_seconds() == 0) + if (items.find(MetaData::Type::Duration) == items.end() + || boost::any_cast(items[MetaData::Type::Duration]).total_seconds() == 0) { LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Skipped '" << file << "' (no duration or duration 0)"; @@ -310,8 +310,8 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) // ***** Title std::string title; - if (items.find(MetaData::Title) != items.end()) { - title = boost::any_cast(items[MetaData::Title]); + if (items.find(MetaData::Type::Title) != items.end()) { + title = boost::any_cast(items[MetaData::Type::Title]); } else { @@ -322,9 +322,9 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) // ***** Artist Wt::Dbo::ptr artist; - if (items.find(MetaData::Artist) != items.end()) + if (items.find(MetaData::Type::Artist) != items.end()) { - const std::string artistName (boost::any_cast(items[MetaData::Artist])); + const std::string artistName (boost::any_cast(items[MetaData::Type::Artist])); artist = Artist::getByName(_db.getSession(), artistName ); if (!artist) artist = Artist::create( _db.getSession(), artistName ); @@ -337,9 +337,9 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) // ***** Release Wt::Dbo::ptr release; - if (items.find(MetaData::Album) != items.end()) + if (items.find(MetaData::Type::Album) != items.end()) { - const std::string albumName (boost::any_cast(items[MetaData::Album])); + const std::string albumName (boost::any_cast(items[MetaData::Type::Album])); release = Release::getByName(_db.getSession(), albumName); if (!release) release = Release::create( _db.getSession(), albumName ); @@ -354,9 +354,9 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) typedef std::list GenreList; GenreList genreList; std::vector< Genre::pointer > genres; - if (items.find(MetaData::Genres) != items.end()) + if (items.find(MetaData::Type::Genres) != items.end()) { - genreList = (boost::any_cast(items[MetaData::Genres])); + genreList = (boost::any_cast(items[MetaData::Type::Genres])); BOOST_FOREACH(const std::string& genre, genreList) { Genre::pointer dbGenre ( Genre::getByName(_db.getSession(), genre) ); @@ -406,17 +406,26 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) track.modify()->setArtist( artist ); track.modify()->setRelease( release ); - if (items.find(MetaData::TrackNumber) != items.end()) - track.modify()->setTrackNumber( boost::any_cast(items[MetaData::TrackNumber]) ); + if (items.find(MetaData::Type::TrackNumber) != items.end()) + track.modify()->setTrackNumber( boost::any_cast(items[MetaData::Type::TrackNumber]) ); - if (items.find(MetaData::DiscNumber) != items.end()) - track.modify()->setDiscNumber( boost::any_cast(items[MetaData::DiscNumber]) ); + if (items.find(MetaData::Type::DiscNumber) != items.end()) + track.modify()->setDiscNumber( boost::any_cast(items[MetaData::Type::DiscNumber]) ); - if (items.find(MetaData::Duration) != items.end()) - track.modify()->setDuration( boost::any_cast(items[MetaData::Duration]) ); + if (items.find(MetaData::Type::Duration) != items.end()) + track.modify()->setDuration( boost::any_cast(items[MetaData::Type::Duration]) ); - if (items.find(MetaData::CreationTime) != items.end()) - track.modify()->setCreationTime( boost::any_cast(items[MetaData::CreationTime]) ); + if (items.find(MetaData::Type::Date) != items.end()) + track.modify()->setDate( boost::any_cast(items[MetaData::Type::Date]) ); + + if (items.find(MetaData::Type::OriginalDate) != items.end()) + { + track.modify()->setOriginalDate( boost::any_cast(items[MetaData::Type::OriginalDate]) ); + + // If a file has an OriginalDate but no date, set the date to ease filtering + if (items.find(MetaData::Type::Date) == items.end()) + track.modify()->setDate( boost::any_cast(items[MetaData::Type::OriginalDate]) ); + } transaction.commit(); @@ -616,8 +625,8 @@ Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats) // We estimate this is a video if: // - we found a least one video stream // - the duration is not null - if (items.find(MetaData::VideoStreams) == items.end() - || boost::any_cast >(items[MetaData::VideoStreams]).empty()) + if (items.find(MetaData::Type::VideoStreams) == items.end() + || boost::any_cast >(items[MetaData::Type::VideoStreams]).empty()) { LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Skipped '" << file << "' (no video stream found)"; @@ -628,8 +637,8 @@ Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats) } return; } - if (items.find(MetaData::Duration) == items.end() - || boost::any_cast(items[MetaData::Duration]).total_seconds() == 0) + if (items.find(MetaData::Type::Duration) == items.end() + || boost::any_cast(items[MetaData::Type::Duration]).total_seconds() == 0) { LMS_LOG(MOD_DBUPDATER, SEV_ERROR) << "Skipped '" << file << "' (no duration or duration 0)"; @@ -659,7 +668,7 @@ Updater::processVideoFile( const boost::filesystem::path& file, Stats& stats) assert(video); video.modify()->setName( file.filename().string() ); - video.modify()->setDuration( boost::any_cast(items[MetaData::Duration]) ); + video.modify()->setDuration( boost::any_cast(items[MetaData::Type::Duration]) ); video.modify()->setLastWriteTime(lastWriteTime); transaction.commit(); diff --git a/src/database/AudioTypes.hpp b/src/database/AudioTypes.hpp index cb18542b..dc912d60 100644 --- a/src/database/AudioTypes.hpp +++ b/src/database/AudioTypes.hpp @@ -196,7 +196,8 @@ class Track void setDuration(boost::posix_time::time_duration duration) { _duration = duration; } void setLastWriteTime(boost::posix_time::ptime time) { _fileLastWrite = time; } void setChecksum(const std::vector& checksum) { _fileChecksum = checksum; } - void setCreationTime(const boost::posix_time::ptime& time) { _creationTime = time; } + void setDate(const boost::posix_time::ptime& date) { _date = date; } + void setOriginalDate(const boost::posix_time::ptime& date) { _originalDate = date; } void setGenres(const std::string& genreList) { _genreList = genreList; } void setGenres(std::vector genres); void setArtist(Artist::pointer artist) { _artist = artist; } @@ -207,7 +208,8 @@ class Track std::string getName(void) const { return _name; } const std::string& getPath(void) const { return _filePath; } boost::posix_time::time_duration getDuration(void) const { return _duration; } - boost::posix_time::ptime getCreationTime(void) const { return _creationTime; } + boost::posix_time::ptime getDate(void) const { return _date; } + boost::posix_time::ptime getOriginalDate(void) const { return _originalDate; } Artist::pointer getArtist(void) const { return _artist; } Release::pointer getRelease(void) const { return _release; } bool hasGenre(Genre::pointer genre) const { return _genres.count(genre); } @@ -223,7 +225,8 @@ class Track Wt::Dbo::field(a, _discNumber, "disc_number"); Wt::Dbo::field(a, _name, "name"); Wt::Dbo::field(a, _duration, "duration"); - Wt::Dbo::field(a, _creationTime, "creation_time"); + Wt::Dbo::field(a, _date, "date"); + Wt::Dbo::field(a, _originalDate, "original_date"); Wt::Dbo::field(a, _genreList, "genre_list"); Wt::Dbo::field(a, _filePath, "path"); Wt::Dbo::field(a, _fileLastWrite, "last_write"); @@ -241,7 +244,8 @@ class Track int _discNumber; std::string _name; boost::posix_time::time_duration _duration; - boost::posix_time::ptime _creationTime; + boost::posix_time::ptime _date; + boost::posix_time::ptime _originalDate; std::string _genreList; std::string _filePath; std::vector _fileChecksum; diff --git a/src/metadata/AvFormat.cpp b/src/metadata/AvFormat.cpp index 878d7862..d7d9d87f 100644 --- a/src/metadata/AvFormat.cpp +++ b/src/metadata/AvFormat.cpp @@ -103,16 +103,16 @@ AvFormat::parse(const boost::filesystem::path& p, Items& items) } if (!videoStreams.empty()) - items.insert( std::make_pair(MetaData::VideoStreams, videoStreams)); + items.insert( std::make_pair(MetaData::Type::VideoStreams, videoStreams)); if (!audioStreams.empty()) - items.insert( std::make_pair(MetaData::AudioStreams, audioStreams)); + items.insert( std::make_pair(MetaData::Type::AudioStreams, audioStreams)); if (!subtitleStreams.empty()) - items.insert( std::make_pair(MetaData::SubtitleStreams, SubtitleStreams)); + items.insert( std::make_pair(MetaData::Type::SubtitleStreams, subtitleStreams)); } // Duration - items.insert( std::make_pair(MetaData::Duration, boost::posix_time::time_duration( boost::posix_time::seconds( input.getDurationSecs() )) )); + items.insert( std::make_pair(MetaData::Type::Duration, boost::posix_time::time_duration( boost::posix_time::seconds( input.getDurationSecs() )) )); // Embedded MetaData // Make sure to convert strings into UTF-8 @@ -120,38 +120,42 @@ AvFormat::parse(const boost::filesystem::path& p, Items& items) for (it = metadata.begin(); it != metadata.end(); ++it) { if (boost::iequals(it->first, "artist")) - items.insert( std::make_pair(MetaData::Artist, string_trim( string_to_utf8(it->second)) )); + items.insert( std::make_pair(MetaData::Type::Artist, string_trim( string_to_utf8(it->second)) )); else if (boost::iequals(it->first, "album")) - items.insert( std::make_pair(MetaData::Album, string_trim( string_to_utf8(it->second)) )); + items.insert( std::make_pair(MetaData::Type::Album, string_trim( string_to_utf8(it->second)) )); else if (boost::iequals(it->first, "title")) - items.insert( std::make_pair(MetaData::Title, string_trim( string_to_utf8(it->second)) )); + items.insert( std::make_pair(MetaData::Type::Title, string_trim( string_to_utf8(it->second)) )); else if (boost::iequals(it->first, "track")) { std::size_t number; if (readAs(it->second, number)) - items.insert( std::make_pair(MetaData::TrackNumber, number )); + items.insert( std::make_pair(MetaData::Type::TrackNumber, number )); } else if (boost::iequals(it->first, "disc")) { std::size_t number; if (readAs(it->second, number)) - items.insert( std::make_pair(MetaData::DiscNumber, number )); + items.insert( std::make_pair(MetaData::Type::DiscNumber, number )); } else if (boost::iequals(it->first, "date") || boost::iequals(it->first, "year") - || boost::iequals(it->first, "WM/Year") - || boost::iequals(it->first, "TDOR") // Original date fallback - || boost::iequals(it->first, "TORY") // Original date fallback - ) + || boost::iequals(it->first, "WM/Year")) { boost::posix_time::ptime p; if (readAsPosixTime(it->second, p)) - items.insert( std::make_pair(MetaData::CreationTime, p)); + items.insert( std::make_pair(MetaData::Type::Date, p)); + } + else if (boost::iequals(it->first, "TDOR") // Original release time (ID3v2 2.4) + || boost::iequals(it->first, "TORY")) // Original release year + { + boost::posix_time::ptime p; + if (readAsPosixTime(it->second, p)) + items.insert( std::make_pair(MetaData::Type::OriginalDate, p)); } else if (boost::iequals(it->first, "genre")) { std::list genres; if (readList(it->second, ";,", genres)) - items.insert( std::make_pair(MetaData::Genres, genres)); + items.insert( std::make_pair(MetaData::Type::Genres, genres)); } /* else diff --git a/src/metadata/MetaData.hpp b/src/metadata/MetaData.hpp index 58b76373..93fc1154 100644 --- a/src/metadata/MetaData.hpp +++ b/src/metadata/MetaData.hpp @@ -28,7 +28,7 @@ namespace MetaData { - enum Type + enum class Type { Artist, // string Title, // string @@ -37,7 +37,8 @@ namespace MetaData Duration, // boost::posix_time::time_duration TrackNumber, // size_t DiscNumber, // size_t - CreationTime, // boost::posix_time::ptime + Date, // boost::posix_time::ptime + OriginalDate, // boost::posix_time::ptime Cover, // GenericData AudioStreams, // vector VideoStreams, // vector diff --git a/src/remote/server/AudioCollectionRequestHandler.cpp b/src/remote/server/AudioCollectionRequestHandler.cpp index 5eb8e004..5b9fc383 100644 --- a/src/remote/server/AudioCollectionRequestHandler.cpp +++ b/src/remote/server/AudioCollectionRequestHandler.cpp @@ -284,8 +284,12 @@ AudioCollectionRequestHandler::processGetTracks(const AudioCollectionRequest::Ge track->set_name( std::string( boost::locale::conv::to_utf((*it)->getName(), "UTF-8") ) ); track->set_duration_secs( (*it)->getDuration().total_seconds() ); -// if (!(*it)->getCreationTime().is_special()) -// track->set_release_date( boost::posix_time::to_simple_string((*it)->getCreationTime()) ); + + // Only send the year part of the release times + if (!(*it)->getDate().is_special()) + track->set_release_date( std::to_string((*it)->getDate().date().year()) ); + if (!(*it)->getOriginalDate().is_special()) + track->set_original_release_date( std::to_string((*it)->getOriginalDate().date().year()) ); BOOST_FOREACH(Database::Genre::pointer genre, (*it)->getGenres()) track->add_genre_id( genre.id() ); diff --git a/src/ui/audio/TrackWidget.cpp b/src/ui/audio/TrackWidget.cpp index 74c73385..8f1f1bb4 100644 --- a/src/ui/audio/TrackWidget.cpp +++ b/src/ui/audio/TrackWidget.cpp @@ -44,7 +44,8 @@ _trackStats(nullptr) _queryModel.addColumn( "track.track_number", "Track #" ); _queryModel.addColumn( "track.name", "Track" ); _queryModel.addColumn( "track.duration", "Duration" ); - _queryModel.addColumn( "track.creation_time", "Date" ); + _queryModel.addColumn( "track.date", "Date" ); + _queryModel.addColumn( "track.original_date", "Original Date" ); _queryModel.addColumn( "track.genre_list", "Genres" ); _queryModel.setBatchSize(1000); @@ -57,16 +58,24 @@ _trackStats(nullptr) _tableView->setAlternatingRowColors(true); _tableView->setModel(&_queryModel); + // Duration display + { + // TODO better handle 1 hour+ files! + Wt::WItemDelegate *delegate = new Wt::WItemDelegate(this); + delegate->setTextFormat("mm:ss"); + _tableView->setItemDelegateForColumn(5, delegate); + } + + // Date display, just the year { Wt::WItemDelegate *delegate = new Wt::WItemDelegate(this); delegate->setTextFormat("yyyy"); _tableView->setItemDelegateForColumn(6, delegate); } { - // TODO better handle 1 hour+ files! Wt::WItemDelegate *delegate = new Wt::WItemDelegate(this); - delegate->setTextFormat("mm:ss"); - _tableView->setItemDelegateForColumn(5, delegate); + delegate->setTextFormat("yyyy"); + _tableView->setItemDelegateForColumn(7, delegate); } // TODO other event! @@ -101,7 +110,7 @@ TrackWidget::refresh(const Constraint& constraint) Wt::Dbo::Query query = _db.getSession().query( sqlQuery.get() ); - query.groupBy("track").orderBy("artist.name,track.creation_time,release.name,track.disc_number,track.track_number"); + query.groupBy("track").orderBy("artist.name,track.date,release.name,track.disc_number,track.track_number"); BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) { LMS_LOG(MOD_UI, SEV_DEBUG) << "Binding value '" << bindArg << "'"; diff --git a/test/RemoteClient.cpp b/test/RemoteClient.cpp index 69903354..23f3b384 100644 --- a/test/RemoteClient.cpp +++ b/test/RemoteClient.cpp @@ -83,12 +83,20 @@ struct TrackInfo boost::posix_time::time_duration duration; + std::string date; + std::string original_date; + TrackInfo(): id(0), release_id(0), artist_id(0), disc_number(0), track_number(0) {} }; std::ostream& operator<<(std::ostream& os, const TrackInfo& info) { os << "id = " << info.id << ", name = '" << info.name << "', track_number = " << info.track_number << ", duration = " << info.duration; + if (!info.date.empty()) + os << ", date = " << info.date; + if (!info.original_date.empty()) + os << ", original date = " << info.original_date; + return os; } @@ -352,6 +360,12 @@ class TestClient if (response.audio_collection_response().track_list().tracks(i).has_disc_number()) track.disc_number = response.audio_collection_response().track_list().tracks(i).disc_number(); + if (response.audio_collection_response().track_list().tracks(i).has_release_date()) + track.date = response.audio_collection_response().track_list().tracks(i).release_date(); + + if (response.audio_collection_response().track_list().tracks(i).has_original_release_date()) + track.original_date = response.audio_collection_response().track_list().tracks(i).original_release_date(); + tracks.push_back( track ); nbAdded++; @@ -756,7 +770,7 @@ int main() TestClient client( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4::loopback(), 5080)); // Use a dumb account in order to login TODO parametrize - if (!client.login("admin", "admin")) + if (!client.login("admin", "toto")) throw std::runtime_error("login failed!"); // **** REVISION ***