Simplified a bit the AvInfo part
This commit is contained in:
+33
-105
@@ -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
@@ -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
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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) {}
|
||||||
|
};
|
||||||
|
|
||||||
Reference in New Issue
Block a user