Simplified a bit the AvInfo part

This commit is contained in:
emeric
2018-03-21 23:12:14 +01:00
parent 4683a9ef1e
commit 937729f8ff
8 changed files with 211 additions and 259 deletions
+33 -105
View File
@@ -25,18 +25,6 @@
namespace Av { 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) static std::string averror_to_string(int error)
{ {
std::array<char, 128> buf = {0}; std::array<char, 128> buf = {0};
@@ -47,6 +35,10 @@ static std::string averror_to_string(int error)
return "Unknown error"; return "Unknown error";
} }
MediaFileException::MediaFileException(int avError)
: LmsException("MediaFileException: " + averror_to_string(avError))
{
}
void AvInit() void AvInit()
{ {
@@ -62,52 +54,30 @@ void AvInit()
MediaFile::MediaFile(const boost::filesystem::path& p) MediaFile::MediaFile(const boost::filesystem::path& p)
: _p(p), _context(nullptr) : _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() MediaFile::~MediaFile()
{ {
if (_context != nullptr) avformat_close_input(&_context);
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;
} }
boost::posix_time::time_duration boost::posix_time::time_duration
MediaFile::getDuration() const MediaFile::getDuration() const
{ {
if (_context == nullptr)
throw std::logic_error("inputfile '" + _p.string() + "' not open");
if (static_cast<int>(_context->duration) != AV_NOPTS_VALUE ) if (static_cast<int>(_context->duration) != AV_NOPTS_VALUE )
return boost::posix_time::seconds(_context->duration / AV_TIME_BASE); return boost::posix_time::seconds(_context->duration / AV_TIME_BASE);
else else
@@ -130,9 +100,6 @@ getMetaDataFromDictionnary(AVDictionary* dictionnary, std::map<std::string, std:
std::map<std::string, std::string> std::map<std::string, std::string>
MediaFile::getMetaData(void) MediaFile::getMetaData(void)
{ {
if (_context == nullptr)
throw std::logic_error("inputfile '" + _p.string() + "' not open");
std::map<std::string, std::string> res; std::map<std::string, std::string> res;
getMetaDataFromDictionnary(_context->metadata, res); getMetaDataFromDictionnary(_context->metadata, res);
@@ -153,13 +120,10 @@ MediaFile::getMetaData(void)
return res; return res;
} }
std::vector<Stream> std::vector<StreamInfo>
MediaFile::getStreams(Stream::Type type) const MediaFile::getStreamInfo() const
{ {
if (_context == nullptr) std::vector<StreamInfo> res;
throw std::logic_error("inputfile '" + _p.string() + "' not open");
std::vector<Stream> res;
for (std::size_t i = 0; i < _context->nb_streams; ++i) 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) if (avstream->disposition & AV_DISPOSITION_ATTACHED_PIC)
continue; 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; continue;
} }
if (type == Stream::Type::Audio && avstream->codec->codec_type != AVMEDIA_TYPE_AUDIO) if (avstream->codecpar->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)
continue; continue;
Stream stream; res.push_back( {.id = i, .bitrate = static_cast<std::size_t>(avstream->codecpar->bit_rate)} );
stream.id = i; // or use stream->id ?
stream.type = type;
stream.bitrate = avstream->codec->bit_rate;
{
std::array<char, 256> buf = {0};
avcodec_string(buf.data(), buf.size(), avstream->codec, 0);
stream.desc = buf.data();
}
res.push_back(stream);
} }
return res; return res;
} }
boost::optional<std::size_t> boost::optional<std::size_t>
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, int res = av_find_best_stream(_context,
avMediaType, AVMEDIA_TYPE_AUDIO,
-1, // Auto -1, // Auto
-1, // Auto -1, // Auto
NULL, NULL,
0); 0);
if (res < 0) if (res < 0)
{
LMS_LOG(AV, ERROR) << "Cannot find best stream for type " << streamType_to_string(type);
return boost::none; return boost::none;
}
return res; return res;
} }
@@ -248,9 +179,6 @@ MediaFile::hasAttachedPictures(void) const
std::vector<Picture> std::vector<Picture>
MediaFile::getAttachedPictures(std::size_t nbMaxPictures) const MediaFile::getAttachedPictures(std::size_t nbMaxPictures) const
{ {
if (_context == nullptr)
throw std::logic_error("inputfile '" + _p.string() + "' not open");
static const std::map<int, std::string> codecMimeMap = static const std::map<int, std::string> codecMimeMap =
{ {
{ AV_CODEC_ID_BMP, "image/x-bmp" }, { 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)) if (!(avstream->disposition & AV_DISPOSITION_ATTACHED_PIC))
continue; 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; continue;
} }
Picture picture; Picture picture;
auto itMime = codecMimeMap.find(avstream->codec->codec_id); auto itMime = codecMimeMap.find(avstream->codecpar->codec_id);
if (itMime != codecMimeMap.end()) if (itMime != codecMimeMap.end())
{ {
picture.mimeType = itMime->second; picture.mimeType = itMime->second;
@@ -287,7 +215,7 @@ MediaFile::getAttachedPictures(std::size_t nbMaxPictures) const
else else
{ {
picture.mimeType = "application/octet-stream"; 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; AVPacket pkt = avstream->attached_pic;
+14 -23
View File
@@ -19,8 +19,7 @@
/* This file contains some classes in order to get info from file using the libavconv */ /* This file contains some classes in order to get info from file using the libavconv */
#ifndef AV_INFO_HPP #pragma once
#define AV_INFO_HPP
extern "C" extern "C"
{ {
@@ -39,6 +38,8 @@ extern "C"
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp> //no i/o just types #include <boost/date_time/posix_time/posix_time_types.hpp> //no i/o just types
#include "utils/Exception.hpp"
namespace Av namespace Av
{ {
@@ -50,25 +51,21 @@ struct Picture
std::vector<uint8_t> data; std::vector<uint8_t> data;
}; };
struct Stream struct StreamInfo
{ {
enum class Type size_t id;
{
Audio,
Video,
Subtitle,
};
int id;
Type type;
std::size_t bitrate; std::size_t bitrate;
std::string desc; // Description of the stream };
class MediaFileException : public LmsException
{
public:
MediaFileException(int avError);
}; };
class MediaFile class MediaFile
{ {
public: public:
MediaFile(const boost::filesystem::path& p); MediaFile(const boost::filesystem::path& p);
~MediaFile(); ~MediaFile();
@@ -76,16 +73,13 @@ class MediaFile
MediaFile(const MediaFile&) = delete; MediaFile(const MediaFile&) = delete;
MediaFile& operator=(const MediaFile&) = delete; MediaFile& operator=(const MediaFile&) = delete;
boost::filesystem::path getPath() const {return _p;}; const boost::filesystem::path& getPath() const {return _p;};
bool open(void);
bool scan(void);
boost::posix_time::time_duration getDuration() const; boost::posix_time::time_duration getDuration() const;
std::map<std::string, std::string> getMetaData(void); std::map<std::string, std::string> getMetaData(void);
std::vector<Stream> getStreams(Stream::Type type) const; std::vector<StreamInfo> getStreamInfo() const;
boost::optional<std::size_t> getBestStreamId(Stream::Type type) const; // none if failure/unknown boost::optional<std::size_t> getBestStream() const; // none if failure/unknown
bool hasAttachedPictures(void) const; bool hasAttachedPictures(void) const;
std::vector<Picture> getAttachedPictures(std::size_t nbMaxPictures) const; std::vector<Picture> getAttachedPictures(std::size_t nbMaxPictures) const;
@@ -96,8 +90,5 @@ class MediaFile
AVFormatContext* _context; AVFormatContext* _context;
}; };
} // namespace Av } // namespace Av
#endif
+10 -4
View File
@@ -130,12 +130,18 @@ Grabber::getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t
std::vector<Image::Image> std::vector<Image::Image>
Grabber::getFromTrack(const boost::filesystem::path& p, std::size_t nbMaxCovers) const 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); return getFromAvMediaFile(input, nbMaxCovers);
else }
return std::vector<Image::Image>(); catch (Av::MediaFileException& e)
{
LMS_LOG(COVER, ERROR) << "Cannot get covers from track " << p << ": " << e.what();
}
return std::vector<Image::Image>();
} }
std::vector<Image::Image> std::vector<Image::Image>
+107 -112
View File
@@ -40,145 +40,140 @@ AvFormat::parse(const boost::filesystem::path& p)
{ {
Items items; Items items;
Av::MediaFile mediaFile(p); try
if (!mediaFile.open())
return boost::none;
if (!mediaFile.scan())
return boost::none;
// Stream info
{ {
std::vector<AudioStream> audioStreams; Av::MediaFile mediaFile(p);
std::vector<Av::Stream> streams = mediaFile.getStreams(Av::Stream::Type::Audio); // Stream info
for (Av::Stream& stream : streams)
{ {
AudioStream audioStream; std::vector<AudioStream> audioStreams;
audioStream.desc = stream.desc;
audioStream.bitRate = stream.bitrate;
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()) // Duration
items.insert( std::make_pair(MetaData::Type::AudioStreams, audioStreams)); items.insert( std::make_pair(MetaData::Type::Duration, mediaFile.getDuration() ));
}
// Duration // Cover
items.insert( std::make_pair(MetaData::Type::Duration, mediaFile.getDuration() )); items.insert( std::make_pair(MetaData::Type::HasCover, mediaFile.hasAttachedPictures()));
// Cover // Embedded MetaData
items.insert( std::make_pair(MetaData::Type::HasCover, mediaFile.hasAttachedPictures())); // Make sure to convert strings into UTF-8
// Embedded MetaData MetaData::Clusters clusters;
// Make sure to convert strings into UTF-8
MetaData::Clusters clusters; std::map<std::string, std::string> metadataMap = mediaFile.getMetaData();
for (auto metadata : metadataMap)
std::map<std::string, std::string> metadataMap = mediaFile.getMetaData(); {
for (auto metadata : metadataMap) const std::string tag = boost::to_upper_copy<std::string>(metadata.first);
{ const std::string value = metadata.second;
const std::string tag = boost::to_upper_copy<std::string>(metadata.first);
const std::string value = metadata.second;
#if 0 #if 0
std::cout << "TAG = " << tag << ", VAL = " << value << std::endl; std::cout << "TAG = " << tag << ", VAL = " << value << std::endl;
#endif #endif
if (tag == "ARTIST") if (tag == "ARTIST")
items.insert( std::make_pair(MetaData::Type::Artist, stringTrim( stringToUTF8(value)) )); items.insert( std::make_pair(MetaData::Type::Artist, stringTrim( stringToUTF8(value)) ));
else if (tag == "ALBUM") else if (tag == "ALBUM")
items.insert( std::make_pair(MetaData::Type::Album, stringTrim( stringToUTF8(value)) )); items.insert( std::make_pair(MetaData::Type::Album, stringTrim( stringToUTF8(value)) ));
else if (tag == "TITLE") else if (tag == "TITLE")
items.insert( std::make_pair(MetaData::Type::Title, stringTrim( stringToUTF8(value)) )); items.insert( std::make_pair(MetaData::Type::Title, stringTrim( stringToUTF8(value)) ));
else if (tag == "TRACK") else if (tag == "TRACK")
{
// Expecting 'Number/Total'
auto strings = splitString(value, "/");
if (strings.size() > 0)
{ {
std::size_t number; // Expecting 'Number/Total'
if (readAs<std::size_t>(strings[0], number)) auto strings = splitString(value, "/");
items.insert( std::make_pair(MetaData::Type::TrackNumber, number ));
if (strings.size() > 1) if (strings.size() > 0)
{ {
std::size_t totalNumber; std::size_t number;
if (readAs<std::size_t>(strings[1], totalNumber)) if (readAs<std::size_t>(strings[0], number))
items.insert( std::make_pair(MetaData::Type::TotalTrack, totalNumber )); items.insert( std::make_pair(MetaData::Type::TrackNumber, number ));
if (strings.size() > 1)
{
std::size_t totalNumber;
if (readAs<std::size_t>(strings[1], totalNumber))
items.insert( std::make_pair(MetaData::Type::TotalTrack, totalNumber ));
}
} }
} }
} else if (tag == "DISC")
else if (tag == "DISC")
{
// Expecting 'Number/Total'
auto strings = splitString(value, "/");
if (strings.size() > 0)
{ {
std::size_t number; // Expecting 'Number/Total'
if (readAs<std::size_t>(strings[0], number)) auto strings = splitString(value, "/");
items.insert( std::make_pair(MetaData::Type::DiscNumber, number ));
if (strings.size() > 1) if (strings.size() > 0)
{ {
std::size_t totalNumber; std::size_t number;
if (readAs<std::size_t>(strings[1], totalNumber)) if (readAs<std::size_t>(strings[0], number))
items.insert( std::make_pair(MetaData::Type::TotalDisc, totalNumber )); items.insert( std::make_pair(MetaData::Type::DiscNumber, number ));
if (strings.size() > 1)
{
std::size_t totalNumber;
if (readAs<std::size_t>(strings[1], totalNumber))
items.insert( std::make_pair(MetaData::Type::TotalDisc, totalNumber ));
}
} }
} }
} else if (tag == "DATE"
else if (tag == "DATE" || tag == "YEAR"
|| tag == "YEAR" || tag == "WM/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<std::string> clusterNames = splitString(value, ";,\\");
if (!clusterNames.empty())
{ {
clusters[_clusterMap[tag]] = std::set<std::string>(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<std::string> clusterNames = splitString(value, ";,\\");
if (!clusterNames.empty())
{
clusters[_clusterMap[tag]] = std::set<std::string>(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; return items;
} }
+1 -1
View File
@@ -56,7 +56,7 @@ namespace MetaData
// Used by Streams // Used by Streams
struct AudioStream struct AudioStream
{ {
std::string desc; // TODO codec?
std::size_t bitRate; std::size_t bitRate;
}; };
+1 -1
View File
@@ -59,7 +59,7 @@ TagLibParser::parse(const boost::filesystem::path& p)
items.insert( std::make_pair(MetaData::Type::Duration, duration) ); items.insert( std::make_pair(MetaData::Type::Duration, duration) );
MetaData::AudioStream audioStream = { .desc = "", .bitRate = static_cast<std::size_t>(properties->bitrate() * 1000) }; MetaData::AudioStream audioStream = { .bitRate = static_cast<std::size_t>(properties->bitrate() * 1000) };
items.insert( std::make_pair(MetaData::Type::AudioStreams, std::vector<MetaData::AudioStream>(1, audioStream ) )); items.insert( std::make_pair(MetaData::Type::AudioStreams, std::vector<MetaData::AudioStream>(1, audioStream ) ));
} }
+15 -13
View File
@@ -89,21 +89,23 @@ MediaPlayer::playTrack(Database::Track::id_type trackId)
transaction.commit(); transaction.commit();
// Analyse track, select the best media stream // Analyse track, select the best media stream
Av::MediaFile mediaFile(track->getPath()); try
if (!mediaFile.open() || !mediaFile.scan())
{ {
LMS_LOG(UI, ERROR) << "Cannot open file '" << track->getPath(); Av::MediaFile mediaFile(track->getPath());
return;
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 void
+30
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdexcept>
#include <string>
class LmsException : public std::runtime_error
{
public:
LmsException(const std::string& error) : std::runtime_error(error) {}
};