From a03a890f5bc9b4c08ef9603d1bcadacc1a0a936b Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 13 Dec 2014 18:21:57 +0100 Subject: [PATCH] [UI] Various improvments in audio view --- src/av/InputFormatContext.cpp | 13 +++++++++ src/av/InputFormatContext.hpp | 1 + src/database-updater/DatabaseUpdater.cpp | 5 ++++ src/database/AudioTypes.hpp | 4 +++ src/database/Track.cpp | 3 ++- src/metadata/AvFormat.cpp | 3 +++ src/metadata/MetaData.hpp | 8 +----- src/ui/audio/AudioMediaPlayer.cpp | 11 +++++--- src/ui/audio/PlayQueue.cpp | 33 +++++++++++++---------- src/ui/audio/TableFilter.cpp | 13 +++++---- src/ui/docroot/css/lms.css | 13 +++++++++ src/ui/docroot/images/unknown-cover.jpg | Bin 0 -> 1120 bytes src/ui/settings/Settings.cpp | 1 + 13 files changed, 78 insertions(+), 30 deletions(-) create mode 100755 src/ui/docroot/images/unknown-cover.jpg diff --git a/src/av/InputFormatContext.cpp b/src/av/InputFormatContext.cpp index 346702fe..6fcdb432 100644 --- a/src/av/InputFormatContext.cpp +++ b/src/av/InputFormatContext.cpp @@ -114,6 +114,19 @@ InputFormatContext::getMetadata(void) } +std::size_t +InputFormatContext::getNbPictures(void) const +{ + std::size_t res = 0; + + for (std::size_t i = 0; i < native()->nb_streams; ++i) + { + if (native()->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC) + ++res; + } + return res; +} + void InputFormatContext::getPictures(std::vector< std::vector >& pictures) const { diff --git a/src/av/InputFormatContext.hpp b/src/av/InputFormatContext.hpp index 11fe1c89..91bb2725 100644 --- a/src/av/InputFormatContext.hpp +++ b/src/av/InputFormatContext.hpp @@ -46,6 +46,7 @@ class InputFormatContext : public FormatContext // Get attached pictures + std::size_t getNbPictures(void) const; void getPictures(std::vector< std::vector >& pictures) const; // Get the streams diff --git a/src/database-updater/DatabaseUpdater.cpp b/src/database-updater/DatabaseUpdater.cpp index 22b7e530..37c1c196 100644 --- a/src/database-updater/DatabaseUpdater.cpp +++ b/src/database-updater/DatabaseUpdater.cpp @@ -402,6 +402,11 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) track.modify()->setDate( boost::any_cast(items[MetaData::Type::OriginalDate]) ); } + if (items.find(MetaData::Type::HasCover) != items.end()) + { + track.modify()->setHasCover( boost::any_cast(items[MetaData::Type::HasCover])); + } + transaction.commit(); } diff --git a/src/database/AudioTypes.hpp b/src/database/AudioTypes.hpp index ddc21bf6..08ccc1d5 100644 --- a/src/database/AudioTypes.hpp +++ b/src/database/AudioTypes.hpp @@ -117,6 +117,7 @@ class Track void setOriginalDate(const boost::posix_time::ptime& date) { _originalDate = date; } void setGenres(const std::string& genreList) { _genreList = genreList; } void setGenres(std::vector genres); + void setHasCover(bool hasCover) { _hasCover = hasCover; } int getTrackNumber(void) const { return _trackNumber; } int getDiscNumber(void) const { return _discNumber; } @@ -132,6 +133,7 @@ class Track boost::posix_time::ptime getLastWriteTime(void) const { return _fileLastWrite; } const std::vector& getChecksum(void) const { return _fileChecksum; } + bool hasCover(void) const { return _hasCover; } template void persist(Action& a) @@ -148,6 +150,7 @@ class Track Wt::Dbo::field(a, _filePath, "path"); Wt::Dbo::field(a, _fileLastWrite, "last_write"); Wt::Dbo::field(a, _fileChecksum, "checksum"); + Wt::Dbo::field(a, _hasCover, "has_cover"); Wt::Dbo::hasMany(a, _genres, Wt::Dbo::ManyToMany, "track_genre", "", Wt::Dbo::OnDeleteCascade); } @@ -167,6 +170,7 @@ class Track std::string _filePath; std::vector _fileChecksum; boost::posix_time::ptime _fileLastWrite; + bool _hasCover; Wt::Dbo::collection< Genre::pointer > _genres; // Tracks that belong to this genre }; diff --git a/src/database/Track.cpp b/src/database/Track.cpp index d0e966cb..f2df60e2 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -29,7 +29,8 @@ Track::Track(const boost::filesystem::path& p) : _trackNumber(0), _discNumber(0), -_filePath( p.string() ) +_filePath( p.string() ), +_hasCover(false) { } diff --git a/src/metadata/AvFormat.cpp b/src/metadata/AvFormat.cpp index d7d9d87f..821d6191 100644 --- a/src/metadata/AvFormat.cpp +++ b/src/metadata/AvFormat.cpp @@ -114,6 +114,9 @@ AvFormat::parse(const boost::filesystem::path& p, Items& items) // Duration items.insert( std::make_pair(MetaData::Type::Duration, boost::posix_time::time_duration( boost::posix_time::seconds( input.getDurationSecs() )) )); + // Cover + items.insert( std::make_pair(MetaData::Type::HasCover, input.getNbPictures() > 0)); + // Embedded MetaData // Make sure to convert strings into UTF-8 std::map::const_iterator it; diff --git a/src/metadata/MetaData.hpp b/src/metadata/MetaData.hpp index 93fc1154..07dec04d 100644 --- a/src/metadata/MetaData.hpp +++ b/src/metadata/MetaData.hpp @@ -39,18 +39,12 @@ namespace MetaData DiscNumber, // size_t Date, // boost::posix_time::ptime OriginalDate, // boost::posix_time::ptime - Cover, // GenericData + HasCover, // bool AudioStreams, // vector VideoStreams, // vector SubtitleStreams, // vector }; - // Used by Cover - struct GenericData { - std::string mimeType; - std::vector data; - }; - // Used by Streams struct AudioStream { std::size_t nbChannels; diff --git a/src/ui/audio/AudioMediaPlayer.cpp b/src/ui/audio/AudioMediaPlayer.cpp index c3308062..7efba7f8 100644 --- a/src/ui/audio/AudioMediaPlayer.cpp +++ b/src/ui/audio/AudioMediaPlayer.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "AudioMediaPlayer.hpp" @@ -30,6 +31,9 @@ AudioMediaPlayer::AudioMediaPlayer( Wt::WContainerWidget *parent) : Wt::WContainerWidget(parent), _mediaResource(nullptr) { + + this->setStyleClass("mediaplayer"); + _mediaPlayer = new Wt::WMediaPlayer( Wt::WMediaPlayer::Audio, this ); // _mediaPlayer->setAlternativeContent (new Wt::WText("You don't have HTML5 audio support!")); // _mediaPlayer->setOptions( Wt::WMediaPlayer::Autoplay ); @@ -52,14 +56,15 @@ AudioMediaPlayer::AudioMediaPlayer( Wt::WContainerWidget *parent) _shuffle.emit( shuffle->checkState() == Wt::Checked); })); + _curTime = new Wt::WText("00:00:00", container); + _timeSlider = new Wt::WSlider( container ); + _duration = new Wt::WText("00:00:00", container); + Wt::WPushButton *prevBtn = new Wt::WPushButton("<<", container ); _playBtn = new Wt::WPushButton("Play", container ); _pauseBtn = new Wt::WPushButton("Pause", container ); Wt::WPushButton *nextBtn = new Wt::WPushButton(">>", container ); - _curTime = new Wt::WText(container); - _timeSlider = new Wt::WSlider( container ); - _duration = new Wt::WText(container); _volumeSlider = new Wt::WSlider( container ); _volumeSlider->setRange(0,100); diff --git a/src/ui/audio/PlayQueue.cpp b/src/ui/audio/PlayQueue.cpp index 3e13f5cc..8ea21ac7 100644 --- a/src/ui/audio/PlayQueue.cpp +++ b/src/ui/audio/PlayQueue.cpp @@ -20,10 +20,12 @@ #include #include +#include #include #include #include #include +#include #include "resource/CoverResource.hpp" @@ -82,8 +84,8 @@ class TrackSelector void setShuffle(bool enable); void setLoop(bool enable) { _loop = enable; } - int getPrevious(void); - int getNext(void); + int previous(void); + int next(void); int getCurrent(void); // Set the internal pos thanks to the track pos @@ -125,7 +127,7 @@ TrackSelector::refreshPositions() _trackPos.push_back(i); // Now shuffle - // Perform size/2 permutations + // Source: http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle boost::random::mt19937 rng; // produces randomness out of thin air rng.seed(static_cast(std::time(0))); @@ -157,7 +159,7 @@ TrackSelector::setShuffle(bool enable) } int -TrackSelector::getNext() +TrackSelector::next() { if (_size == 0) return trackPosInvalid; @@ -176,7 +178,7 @@ TrackSelector::getNext() } int -TrackSelector::getPrevious() +TrackSelector::previous() { if (_size == 0) return trackPosInvalid; @@ -229,16 +231,17 @@ class PlayQueueItemDelegate : public Wt::WItemDelegate { Wt::WWidget* res; - LMS_LOG(MOD_UI, SEV_DEBUG) << "Update called! widget = " << widget << ", row = " << index.row() << ", col = " << index.column(); - Wt::WString path = Wt::asString(index.data(CoverRole)); if (!path.empty()) { - LMS_LOG(MOD_UI, SEV_DEBUG) << "Path = " << path ; - // Create an image for this track - Wt::WImage *image = new Wt::WImage( ); - CoverResource *resource = new CoverResource(boost::filesystem::path(path.toUTF8()), 64, image); + Wt::WImage* image = new Wt::WImage( ); + Wt::WResource* resource; + if (path != "none") + resource = new CoverResource(boost::filesystem::path(path.toUTF8()), 64, image); + else + resource = new Wt::WFileResource("image/jpeg", Wt::WApplication::instance()->docRoot() + "/images/unknown-cover.jpg", image); + image->setImageLink(Wt::WLink(resource)); res = image; @@ -351,7 +354,7 @@ PlayQueue::addTracks(const std::vector& trackIds) _model->setData(dataRow, COLUMN_ID_TRACK_ID, track.id(), Wt::UserRole); _model->setData(dataRow, COLUMN_ID_POS, dataRow + 1); - _model->setData(dataRow, COLUMN_ID_COVER, track->getPath(), CoverRole); + _model->setData(dataRow, COLUMN_ID_COVER, track->hasCover() ? track->getPath() : "none", CoverRole); _model->setData(dataRow, COLUMN_ID_NAME, track->getArtistName() + " - " + track->getName()); _model->setData(dataRow, COLUMN_ID_DURATION, track->getDuration()); } @@ -383,7 +386,7 @@ PlayQueue::playNext(void) while (nbTries > 0) { - int pos = _trackSelector->getNext(); + int pos = _trackSelector->next(); if (pos == trackPosInvalid) break; @@ -401,7 +404,7 @@ PlayQueue::playPrevious(void) while (nbTries > 0) { - int pos = _trackSelector->getPrevious(); + int pos = _trackSelector->previous(); if (pos == trackPosInvalid) break; @@ -427,6 +430,8 @@ PlayQueue::readTrack(int rowPos) _sigTrackPlay.emit(track->getPath()); + this->scrollTo( _model->index(_trackSelector->getCurrent(), 0)); + return true; } else diff --git a/src/ui/audio/TableFilter.cpp b/src/ui/audio/TableFilter.cpp index 4c5fbf6d..5a521918 100644 --- a/src/ui/audio/TableFilter.cpp +++ b/src/ui/audio/TableFilter.cpp @@ -41,6 +41,8 @@ _field(field) this->setAlternatingRowColors(true); this->setModel(&_queryModel); + this->setColumnWidth(1, 50); + this->selectionChanged().connect(this, &TableFilter::emitUpdate); setLayoutSizeAware(true); @@ -66,14 +68,15 @@ _field(field) void TableFilter::layoutSizeChanged (int width, int height) { - LMS_LOG(MOD_UI, SEV_DEBUG) << "LAYOUT CHANGED!"; + LMS_LOG(MOD_UI, SEV_DEBUG) << "LAYOUT CHANGED, new width = " << width; - /* TODO - std::size_t trackColumnSize = this->columnWidth(1).toPixels() + 30 ; + LMS_LOG(MOD_UI, SEV_DEBUG) << "Before: sizes = " << this->columnWidth(0).toPixels() << ", " << this->columnWidth(1).toPixels(); + + std::size_t trackColumnSize = this->columnWidth(1).toPixels(); // Set the remaining size for the name column - this->setColumnWidth(0, width - 7 - trackColumnSize); - */ + this->setColumnWidth(0, width - 7 - trackColumnSize - 23 /*Asc wdth */); + LMS_LOG(MOD_UI, SEV_DEBUG) << "After: sizes = " << this->columnWidth(0).toPixels() << ", " << this->columnWidth(1).toPixels(); } // Set constraints on this filter diff --git a/src/ui/docroot/css/lms.css b/src/ui/docroot/css/lms.css index 605eaac6..131ffa8d 100644 --- a/src/ui/docroot/css/lms.css +++ b/src/ui/docroot/css/lms.css @@ -2,6 +2,14 @@ margin-bottom: 0px; } +.Wt-hrh2 { + background-color: white; +} + +.Wt-vrh2 { + background-color: white; +} + div.contents { padding: 0px 12px 6px; } @@ -10,3 +18,8 @@ div.contents { font-weight: bold; } +.mediaplayer { + background-color: #ccc; + border-radius: 15px; +} + diff --git a/src/ui/docroot/images/unknown-cover.jpg b/src/ui/docroot/images/unknown-cover.jpg new file mode 100755 index 0000000000000000000000000000000000000000..19ffc70603848bfc9ccb2ba7a57b3e53f3c24ffe GIT binary patch literal 1120 zcmex=J{_I=b4Ls^MSn`pH6GNTmDdP z?|I+tHyG9>&Q93ivSd^73D!R*GH18Gy^(+Iim>?6^%8p|^DUHrPyQtMCD?IM!VCA< zf2F6p*{58(!4sjUJ1MEAW{Vs9kLSi*4Eca;sdE zFGuEGJE!wnq+n+Ci6xxUC#EYGUypsea{E%vsqWFWC-;g!wydo8*$=fBrzRD%0FJN3zx)WtQ>o8Fo^L$tv#wUS8|7S9>B@|de5|Iq_9NeN$0SGIyy;6`7|lrD zdhPkOnH3fl#g7#Nm{@y{?|jbwL7Q7Owp8qNz@;ER)40$>=X{%{`_J6?Pi^XxqpNyX zzVFzeZg48?@X|fe#cm#d59epUt&ch*8zH2!YeJ5r*jB%f)nR87TCC0QT{%7z_ zRNb@WZB^#Oc`Ap}Boa-6l)Rd!Wz5{_eB;-@+`^?w{+f0!>udJwANK8zyr=EO+S28< zf&EH$cn_D#^ofPKKC}E%XFBu5*}P%6DUucOP4=wLk6vZt%>rk<1#e8QTBoBT;1?M% z{V9XBVcPbc*A}nWytK(@(>rS^`-2_FVp5J|xMbMH9Qo&}*SZO+XWIy(ig z=&Y;p)Uhvo`8t7N|56j4_e=hnK0H|{^Rq2#`t-^~>HWX;*B4&DU(Pjk$rr9!Kax6n zB-&UE%oKVZ7yXQiXpR!go%nR;ME#Bi&fG+;%IMeEpI3?OQBlr5+^^{F-aX^5qkPxm z6SFSLyT9MYKHoj~PDo8%bV~@&fy{U+q6X4ZN7! jxT9Vr(de;Z2Sc<;;D#%*hvfq=l-#eKnH923`TtD-N!6}S literal 0 HcmV?d00001 diff --git a/src/ui/settings/Settings.cpp b/src/ui/settings/Settings.cpp index 2f8d4515..4ccc57e1 100644 --- a/src/ui/settings/Settings.cpp +++ b/src/ui/settings/Settings.cpp @@ -51,6 +51,7 @@ _sessionData(sessionData) contents->setStyleClass("contents"); contents->setOverflow(WContainerWidget::OverflowHidden); + // TODO menu style on hover Wt::WMenu *menu = new Wt::WMenu(contents, Wt::Vertical); menu->setStyleClass("nav nav-pills nav-stacked submenu"); menu->setWidth(150);