From 937729f8ffd3ffc53ea87ef82c98902c63396bef Mon Sep 17 00:00:00 2001 From: emeric Date: Wed, 21 Mar 2018 23:12:14 +0100 Subject: [PATCH] Simplified a bit the AvInfo part --- src/av/AvInfo.cpp | 138 +++++---------------- src/av/AvInfo.hpp | 37 +++--- src/cover/CoverArtGrabber.cpp | 14 ++- src/metadata/AvFormat.cpp | 219 +++++++++++++++++----------------- src/metadata/MetaData.hpp | 2 +- src/metadata/TagLibParser.cpp | 2 +- src/ui/MediaPlayer.cpp | 28 +++-- src/utils/Exception.hpp | 30 +++++ 8 files changed, 211 insertions(+), 259 deletions(-) create mode 100644 src/utils/Exception.hpp diff --git a/src/av/AvInfo.cpp b/src/av/AvInfo.cpp index 43f2332f..8ba4551c 100644 --- a/src/av/AvInfo.cpp +++ b/src/av/AvInfo.cpp @@ -25,18 +25,6 @@ namespace Av { -static std::string streamType_to_string(Stream::Type type) -{ - switch (type) - { - case Stream::Type::Audio: return "audio"; - case Stream::Type::Video: return "video"; - case Stream::Type::Subtitle: return "subtitle"; - } - - return "unknown"; -} - static std::string averror_to_string(int error) { std::array buf = {0}; @@ -47,6 +35,10 @@ static std::string averror_to_string(int error) return "Unknown error"; } +MediaFileException::MediaFileException(int avError) +: LmsException("MediaFileException: " + averror_to_string(avError)) +{ +} void AvInit() { @@ -62,52 +54,30 @@ void AvInit() MediaFile::MediaFile(const boost::filesystem::path& p) : _p(p), _context(nullptr) { + int error = avformat_open_input(&_context, _p.string().c_str(), nullptr, nullptr); + if (error < 0) + { + LMS_LOG(AV, ERROR) << "Cannot open " << _p << ": " << averror_to_string(error); + throw MediaFileException(error); + } + + error = avformat_find_stream_info(_context, nullptr); + if (error < 0) + { + LMS_LOG(AV, ERROR) << "Cannot find stream information on " << _p << ": " << averror_to_string(error); + avformat_close_input(&_context); + throw MediaFileException(error); + } } MediaFile::~MediaFile() { - if (_context != nullptr) - avformat_close_input(&_context); -} - -bool -MediaFile::open(void) -{ - if (_context != nullptr) - throw std::logic_error("inputfile '" + _p.string() + "' already open"); - - int error = avformat_open_input(&_context, _p.string().c_str(), nullptr, nullptr); - if (error < 0) - { - LMS_LOG(AV, ERROR) << "Cannot open '" << _p.string() << "': " << averror_to_string(error); - return false; - } - - return true; -} - -bool -MediaFile::scan(void) -{ - if (_context == nullptr) - throw std::logic_error("inputfile '" + _p.string() + "' not open"); - - int error = avformat_find_stream_info(_context, nullptr); - if (error < 0) - { - LMS_LOG(AV, ERROR) << "Cannot find stream information on '" << _p.string() << "': " << averror_to_string(error); - return false; - } - - return true; + avformat_close_input(&_context); } boost::posix_time::time_duration MediaFile::getDuration() const { - if (_context == nullptr) - throw std::logic_error("inputfile '" + _p.string() + "' not open"); - if (static_cast(_context->duration) != AV_NOPTS_VALUE ) return boost::posix_time::seconds(_context->duration / AV_TIME_BASE); else @@ -130,9 +100,6 @@ getMetaDataFromDictionnary(AVDictionary* dictionnary, std::map MediaFile::getMetaData(void) { - if (_context == nullptr) - throw std::logic_error("inputfile '" + _p.string() + "' not open"); - std::map res; getMetaDataFromDictionnary(_context->metadata, res); @@ -153,13 +120,10 @@ MediaFile::getMetaData(void) return res; } -std::vector -MediaFile::getStreams(Stream::Type type) const +std::vector +MediaFile::getStreamInfo() const { - if (_context == nullptr) - throw std::logic_error("inputfile '" + _p.string() + "' not open"); - - std::vector res; + std::vector res; for (std::size_t i = 0; i < _context->nb_streams; ++i) { @@ -169,66 +133,33 @@ MediaFile::getStreams(Stream::Type type) const if (avstream->disposition & AV_DISPOSITION_ATTACHED_PIC) continue; - if (avstream->codec == nullptr) + if (!avstream->codecpar) { - LMS_LOG(AV, ERROR) << "Skipping stream " << i << " since no codec is set"; + LMS_LOG(AV, ERROR) << "Skipping stream " << i << " since no codecpar is set"; continue; } - if (type == Stream::Type::Audio && avstream->codec->codec_type != AVMEDIA_TYPE_AUDIO) - continue; - else if (type == Stream::Type::Video && avstream->codec->codec_type != AVMEDIA_TYPE_VIDEO) - continue; - else if (type == Stream::Type::Subtitle && avstream->codec->codec_type != AVMEDIA_TYPE_SUBTITLE) + if (avstream->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) continue; - Stream stream; - - stream.id = i; // or use stream->id ? - stream.type = type; - stream.bitrate = avstream->codec->bit_rate; - - { - std::array buf = {0}; - avcodec_string(buf.data(), buf.size(), avstream->codec, 0); - - stream.desc = buf.data(); - } - - res.push_back(stream); + res.push_back( {.id = i, .bitrate = static_cast(avstream->codecpar->bit_rate)} ); } return res; } boost::optional -MediaFile::getBestStreamId(Stream::Type type) const +MediaFile::getBestStream() const { - if (_context == nullptr) - throw std::logic_error("inputfile '" + _p.string() + "' not open"); - - enum AVMediaType avMediaType; - - switch (type) - { - case Stream::Type::Audio: avMediaType = AVMEDIA_TYPE_AUDIO; break; - case Stream::Type::Video: avMediaType = AVMEDIA_TYPE_VIDEO; break; - case Stream::Type::Subtitle: avMediaType = AVMEDIA_TYPE_SUBTITLE; break; - default: - return boost::none; - } - int res = av_find_best_stream(_context, - avMediaType, + AVMEDIA_TYPE_AUDIO, -1, // Auto -1, // Auto NULL, 0); + if (res < 0) - { - LMS_LOG(AV, ERROR) << "Cannot find best stream for type " << streamType_to_string(type); return boost::none; - } return res; } @@ -248,9 +179,6 @@ MediaFile::hasAttachedPictures(void) const std::vector MediaFile::getAttachedPictures(std::size_t nbMaxPictures) const { - if (_context == nullptr) - throw std::logic_error("inputfile '" + _p.string() + "' not open"); - static const std::map codecMimeMap = { { AV_CODEC_ID_BMP, "image/x-bmp" }, @@ -271,15 +199,15 @@ MediaFile::getAttachedPictures(std::size_t nbMaxPictures) const if (!(avstream->disposition & AV_DISPOSITION_ATTACHED_PIC)) continue; - if (avstream->codec == nullptr) + if (avstream->codecpar == nullptr) { - LMS_LOG(AV, ERROR) << "Skipping stream " << i << " since no codec is set"; + LMS_LOG(AV, ERROR) << "Skipping stream " << i << " since no codecpar is set"; continue; } Picture picture; - auto itMime = codecMimeMap.find(avstream->codec->codec_id); + auto itMime = codecMimeMap.find(avstream->codecpar->codec_id); if (itMime != codecMimeMap.end()) { picture.mimeType = itMime->second; @@ -287,7 +215,7 @@ MediaFile::getAttachedPictures(std::size_t nbMaxPictures) const else { picture.mimeType = "application/octet-stream"; - LMS_LOG(AV, ERROR) << "CODEC ID " << avstream->codec->codec_id << " not handled in mime type conversion"; + LMS_LOG(AV, ERROR) << "CODEC ID " << avstream->codecpar->codec_id << " not handled in mime type conversion"; } AVPacket pkt = avstream->attached_pic; diff --git a/src/av/AvInfo.hpp b/src/av/AvInfo.hpp index 1bb92e1b..45e44f45 100644 --- a/src/av/AvInfo.hpp +++ b/src/av/AvInfo.hpp @@ -19,8 +19,7 @@ /* This file contains some classes in order to get info from file using the libavconv */ -#ifndef AV_INFO_HPP -#define AV_INFO_HPP +#pragma once extern "C" { @@ -39,6 +38,8 @@ extern "C" #include #include //no i/o just types +#include "utils/Exception.hpp" + namespace Av { @@ -50,25 +51,21 @@ struct Picture std::vector data; }; -struct Stream +struct StreamInfo { - enum class Type - { - Audio, - Video, - Subtitle, - }; - - int id; - Type type; + size_t id; std::size_t bitrate; - std::string desc; // Description of the stream +}; + +class MediaFileException : public LmsException +{ + public: + MediaFileException(int avError); }; class MediaFile { public: - MediaFile(const boost::filesystem::path& p); ~MediaFile(); @@ -76,16 +73,13 @@ class MediaFile MediaFile(const MediaFile&) = delete; MediaFile& operator=(const MediaFile&) = delete; - boost::filesystem::path getPath() const {return _p;}; - - bool open(void); - bool scan(void); + const boost::filesystem::path& getPath() const {return _p;}; boost::posix_time::time_duration getDuration() const; std::map getMetaData(void); - std::vector getStreams(Stream::Type type) const; - boost::optional getBestStreamId(Stream::Type type) const; // none if failure/unknown + std::vector getStreamInfo() const; + boost::optional getBestStream() const; // none if failure/unknown bool hasAttachedPictures(void) const; std::vector getAttachedPictures(std::size_t nbMaxPictures) const; @@ -96,8 +90,5 @@ class MediaFile AVFormatContext* _context; }; - } // namespace Av -#endif - diff --git a/src/cover/CoverArtGrabber.cpp b/src/cover/CoverArtGrabber.cpp index 56967cda..49546400 100644 --- a/src/cover/CoverArtGrabber.cpp +++ b/src/cover/CoverArtGrabber.cpp @@ -130,12 +130,18 @@ Grabber::getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t std::vector Grabber::getFromTrack(const boost::filesystem::path& p, std::size_t nbMaxCovers) const { - Av::MediaFile input(p); + try + { + Av::MediaFile input(p); - if (input.open()) return getFromAvMediaFile(input, nbMaxCovers); - else - return std::vector(); + } + catch (Av::MediaFileException& e) + { + LMS_LOG(COVER, ERROR) << "Cannot get covers from track " << p << ": " << e.what(); + } + + return std::vector(); } std::vector diff --git a/src/metadata/AvFormat.cpp b/src/metadata/AvFormat.cpp index 9b13a731..e2a9c080 100644 --- a/src/metadata/AvFormat.cpp +++ b/src/metadata/AvFormat.cpp @@ -40,145 +40,140 @@ AvFormat::parse(const boost::filesystem::path& p) { Items items; - Av::MediaFile mediaFile(p); - - if (!mediaFile.open()) - return boost::none; - - if (!mediaFile.scan()) - return boost::none; - - // Stream info + try { - std::vector audioStreams; + Av::MediaFile mediaFile(p); - std::vector streams = mediaFile.getStreams(Av::Stream::Type::Audio); - - for (Av::Stream& stream : streams) + // Stream info { - AudioStream audioStream; - audioStream.desc = stream.desc; - audioStream.bitRate = stream.bitrate; + std::vector audioStreams; - audioStreams.push_back(audioStream); + auto streams = mediaFile.getStreamInfo(); + + for (auto stream : streams) + audioStreams.push_back( {.bitRate = stream.bitrate } ); + + if (!audioStreams.empty()) + items.insert( std::make_pair(MetaData::Type::AudioStreams, audioStreams)); } - if (!audioStreams.empty()) - items.insert( std::make_pair(MetaData::Type::AudioStreams, audioStreams)); - } + // Duration + items.insert( std::make_pair(MetaData::Type::Duration, mediaFile.getDuration() )); - // Duration - items.insert( std::make_pair(MetaData::Type::Duration, mediaFile.getDuration() )); + // Cover + items.insert( std::make_pair(MetaData::Type::HasCover, mediaFile.hasAttachedPictures())); - // Cover - items.insert( std::make_pair(MetaData::Type::HasCover, mediaFile.hasAttachedPictures())); + // Embedded MetaData + // Make sure to convert strings into UTF-8 - // Embedded MetaData - // Make sure to convert strings into UTF-8 + MetaData::Clusters clusters; - MetaData::Clusters clusters; - - std::map metadataMap = mediaFile.getMetaData(); - for (auto metadata : metadataMap) - { - const std::string tag = boost::to_upper_copy(metadata.first); - const std::string value = metadata.second; + std::map metadataMap = mediaFile.getMetaData(); + for (auto metadata : metadataMap) + { + const std::string tag = boost::to_upper_copy(metadata.first); + const std::string value = metadata.second; #if 0 - std::cout << "TAG = " << tag << ", VAL = " << value << std::endl; + std::cout << "TAG = " << tag << ", VAL = " << value << std::endl; #endif - if (tag == "ARTIST") - items.insert( std::make_pair(MetaData::Type::Artist, stringTrim( stringToUTF8(value)) )); - else if (tag == "ALBUM") - items.insert( std::make_pair(MetaData::Type::Album, stringTrim( stringToUTF8(value)) )); - else if (tag == "TITLE") - items.insert( std::make_pair(MetaData::Type::Title, stringTrim( stringToUTF8(value)) )); - else if (tag == "TRACK") - { - // Expecting 'Number/Total' - auto strings = splitString(value, "/"); - - if (strings.size() > 0) + if (tag == "ARTIST") + items.insert( std::make_pair(MetaData::Type::Artist, stringTrim( stringToUTF8(value)) )); + else if (tag == "ALBUM") + items.insert( std::make_pair(MetaData::Type::Album, stringTrim( stringToUTF8(value)) )); + else if (tag == "TITLE") + items.insert( std::make_pair(MetaData::Type::Title, stringTrim( stringToUTF8(value)) )); + else if (tag == "TRACK") { - std::size_t number; - if (readAs(strings[0], number)) - items.insert( std::make_pair(MetaData::Type::TrackNumber, number )); + // Expecting 'Number/Total' + auto strings = splitString(value, "/"); - if (strings.size() > 1) + if (strings.size() > 0) { - std::size_t totalNumber; - if (readAs(strings[1], totalNumber)) - items.insert( std::make_pair(MetaData::Type::TotalTrack, totalNumber )); + std::size_t number; + if (readAs(strings[0], number)) + items.insert( std::make_pair(MetaData::Type::TrackNumber, number )); + + if (strings.size() > 1) + { + std::size_t totalNumber; + if (readAs(strings[1], totalNumber)) + items.insert( std::make_pair(MetaData::Type::TotalTrack, totalNumber )); + } } } - } - else if (tag == "DISC") - { - // Expecting 'Number/Total' - auto strings = splitString(value, "/"); - - if (strings.size() > 0) + else if (tag == "DISC") { - std::size_t number; - if (readAs(strings[0], number)) - items.insert( std::make_pair(MetaData::Type::DiscNumber, number )); + // Expecting 'Number/Total' + auto strings = splitString(value, "/"); - if (strings.size() > 1) + if (strings.size() > 0) { - std::size_t totalNumber; - if (readAs(strings[1], totalNumber)) - items.insert( std::make_pair(MetaData::Type::TotalDisc, totalNumber )); + std::size_t number; + if (readAs(strings[0], number)) + items.insert( std::make_pair(MetaData::Type::DiscNumber, number )); + + if (strings.size() > 1) + { + std::size_t totalNumber; + if (readAs(strings[1], totalNumber)) + items.insert( std::make_pair(MetaData::Type::TotalDisc, totalNumber )); + } } } - } - else if (tag == "DATE" - || tag == "YEAR" - || tag == "WM/Year") - { - boost::posix_time::ptime p; - if (readAsPosixTime(value, p)) - items.insert( std::make_pair(MetaData::Type::Date, p)); - } - else if (tag == "TDOR" // Original release time (ID3v2 2.4) - || tag == "TORY") // Original release year - { - boost::posix_time::ptime p; - if (readAsPosixTime(value, p)) - items.insert( std::make_pair(MetaData::Type::OriginalDate, p)); - } - else if (tag == "MUSICBRAINZ ARTIST ID" - || tag == "MUSICBRAINZ_ARTISTID") - { - items.insert( std::make_pair(MetaData::Type::MusicBrainzArtistID, stringTrim( stringToUTF8(value)) )); - } - else if (tag == "MUSICBRAINZ ALBUM ID" - || tag == "MUSICBRAINZ_ALBUMID") - { - items.insert( std::make_pair(MetaData::Type::MusicBrainzAlbumID, stringTrim( stringToUTF8(value)) )); - } - else if (tag == "MUSICBRAINZ RELEASE TRACK ID" - || tag == "MUSICBRAINZ_RELEASETRACKID" - || tag == "MUSICBRAINZ_TRACKID") - { - items.insert( std::make_pair(MetaData::Type::MusicBrainzTrackID, stringTrim( stringToUTF8(value)) )); - } - else if (tag == "ACOUSTID ID") - { - items.insert( std::make_pair(MetaData::Type::AcoustID, stringTrim( stringToUTF8(value)) )); - } - else if (_clusterMap.find(tag) != _clusterMap.end()) - { - std::vector clusterNames = splitString(value, ";,\\"); - - if (!clusterNames.empty()) + else if (tag == "DATE" + || tag == "YEAR" + || tag == "WM/Year") { - clusters[_clusterMap[tag]] = std::set(clusterNames.begin(), clusterNames.end()); + boost::posix_time::ptime p; + if (readAsPosixTime(value, p)) + items.insert( std::make_pair(MetaData::Type::Date, p)); } + else if (tag == "TDOR" // Original release time (ID3v2 2.4) + || tag == "TORY") // Original release year + { + boost::posix_time::ptime p; + if (readAsPosixTime(value, p)) + items.insert( std::make_pair(MetaData::Type::OriginalDate, p)); + } + else if (tag == "MUSICBRAINZ ARTIST ID" + || tag == "MUSICBRAINZ_ARTISTID") + { + items.insert( std::make_pair(MetaData::Type::MusicBrainzArtistID, stringTrim( stringToUTF8(value)) )); + } + else if (tag == "MUSICBRAINZ ALBUM ID" + || tag == "MUSICBRAINZ_ALBUMID") + { + items.insert( std::make_pair(MetaData::Type::MusicBrainzAlbumID, stringTrim( stringToUTF8(value)) )); + } + else if (tag == "MUSICBRAINZ RELEASE TRACK ID" + || tag == "MUSICBRAINZ_RELEASETRACKID" + || tag == "MUSICBRAINZ_TRACKID") + { + items.insert( std::make_pair(MetaData::Type::MusicBrainzTrackID, stringTrim( stringToUTF8(value)) )); + } + else if (tag == "ACOUSTID ID") + { + items.insert( std::make_pair(MetaData::Type::AcoustID, stringTrim( stringToUTF8(value)) )); + } + else if (_clusterMap.find(tag) != _clusterMap.end()) + { + std::vector clusterNames = splitString(value, ";,\\"); + + if (!clusterNames.empty()) + { + clusters[_clusterMap[tag]] = std::set(clusterNames.begin(), clusterNames.end()); + } + } + } + if (!clusters.empty()) + items.insert( std::make_pair(MetaData::Type::Clusters, clusters) ); + } + catch(Av::MediaFileException& e) + { + return items; } - - if (!clusters.empty()) - items.insert( std::make_pair(MetaData::Type::Clusters, clusters) ); return items; } diff --git a/src/metadata/MetaData.hpp b/src/metadata/MetaData.hpp index 6bbea293..c14caed9 100644 --- a/src/metadata/MetaData.hpp +++ b/src/metadata/MetaData.hpp @@ -56,7 +56,7 @@ namespace MetaData // Used by Streams struct AudioStream { - std::string desc; + // TODO codec? std::size_t bitRate; }; diff --git a/src/metadata/TagLibParser.cpp b/src/metadata/TagLibParser.cpp index 5508ef46..27cb2e54 100644 --- a/src/metadata/TagLibParser.cpp +++ b/src/metadata/TagLibParser.cpp @@ -59,7 +59,7 @@ TagLibParser::parse(const boost::filesystem::path& p) items.insert( std::make_pair(MetaData::Type::Duration, duration) ); - MetaData::AudioStream audioStream = { .desc = "", .bitRate = static_cast(properties->bitrate() * 1000) }; + MetaData::AudioStream audioStream = { .bitRate = static_cast(properties->bitrate() * 1000) }; items.insert( std::make_pair(MetaData::Type::AudioStreams, std::vector(1, audioStream ) )); } diff --git a/src/ui/MediaPlayer.cpp b/src/ui/MediaPlayer.cpp index 849ee478..fa9a2003 100644 --- a/src/ui/MediaPlayer.cpp +++ b/src/ui/MediaPlayer.cpp @@ -89,21 +89,23 @@ MediaPlayer::playTrack(Database::Track::id_type trackId) transaction.commit(); // Analyse track, select the best media stream - Av::MediaFile mediaFile(track->getPath()); - - if (!mediaFile.open() || !mediaFile.scan()) + try { - LMS_LOG(UI, ERROR) << "Cannot open file '" << track->getPath(); - return; + Av::MediaFile mediaFile(track->getPath()); + + auto streamId = mediaFile.getBestStream(); + + _audio->pause(); + _audio->clearSources(); + _audio->addSource(LmsApp->getTranscodeResource()->getUrl(trackId, Av::Encoding::MP3, boost::posix_time::seconds(0), streamId)); + _audio->setPreloadMode(Wt::WAudio::PreloadNone); + _audio->play(); + } + catch (Av::MediaFileException& e) + { + LMS_LOG(UI, ERROR) << "MediaFileException: " << e.what(); + stop(); } - - auto streamId = mediaFile.getBestStreamId(Av::Stream::Type::Audio); - - _audio->pause(); - _audio->clearSources(); - _audio->addSource(LmsApp->getTranscodeResource()->getUrl(trackId, Av::Encoding::MP3, boost::posix_time::seconds(0), streamId)); - _audio->setPreloadMode(Wt::WAudio::PreloadNone); - _audio->play(); } void diff --git a/src/utils/Exception.hpp b/src/utils/Exception.hpp new file mode 100644 index 00000000..eda418d9 --- /dev/null +++ b/src/utils/Exception.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include +#include + +class LmsException : public std::runtime_error +{ + public: + LmsException(const std::string& error) : std::runtime_error(error) {} +}; +