diff --git a/configure.ac b/configure.ac
index a670f809..603d8617 100644
--- a/configure.ac
+++ b/configure.ac
@@ -39,6 +39,14 @@ AS_IF([test "$enable_lmsapi" = "yes"],
AM_CONDITIONAL([LMSAPI], [test x$lmsapi = xtrue])
AS_IF([test x$lmsapi = xtrue], [AC_DEFINE(HAVE_LMSAPI, [1], [Enable LMS API support])])
+AC_ARG_ENABLE([video], AS_HELP_STRING([--enable-video],
+ [Enable Video support. Default to yes.])],
+ [enable_video="$enableval"],
+ [enable_video="yes"])
+
+AM_CONDITIONAL([VIDEO], [test x$enable_video = xyes])
+
+
AC_CHECK_HEADERS([Wt/WApplication jpeglib.h],
[],
[AC_MSG_ERROR([Header not found or unusable !])])
diff --git a/src/Makefile.am b/src/Makefile.am
index 7cf37c2d..1b280b43 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -2,12 +2,8 @@ bin_PROGRAMS = lms
lms_SOURCES = \
$(srcdir)/main/main.cpp \
- $(srcdir)/av/CodecContext.cpp \
- $(srcdir)/av/Common.cpp \
- $(srcdir)/av/Dictionary.cpp \
- $(srcdir)/av/FormatContext.cpp \
- $(srcdir)/av/InputFormatContext.cpp \
- $(srcdir)/av/Stream.cpp \
+ $(srcdir)/av/AvInfo.cpp \
+ $(srcdir)/av/AvTranscoder.cpp \
$(srcdir)/cover/CoverArt.cpp \
$(srcdir)/cover/CoverArtGrabber.cpp \
$(srcdir)/database/Artist.cpp \
@@ -27,10 +23,6 @@ lms_SOURCES = \
$(srcdir)/metadata/Utils.cpp \
$(srcdir)/service/ServiceManager.cpp \
$(srcdir)/service/DatabaseUpdateService.cpp \
- $(srcdir)/transcode/AvConvTranscoder.cpp \
- $(srcdir)/transcode/Format.cpp \
- $(srcdir)/transcode/Parameters.cpp \
- $(srcdir)/transcode/InputMediaFile.cpp \
$(srcdir)/ui/LmsApplication.cpp \
$(srcdir)/ui/auth/LmsAuth.cpp \
$(srcdir)/ui/audio/desktop/DesktopAudio.cpp \
@@ -49,10 +41,6 @@ lms_SOURCES = \
$(srcdir)/ui/common/LineEdit.cpp \
$(srcdir)/ui/resource/AvConvTranscodeStreamResource.cpp \
$(srcdir)/ui/resource/CoverResource.cpp \
- $(srcdir)/ui/video/VideoWidget.cpp \
- $(srcdir)/ui/video/VideoDatabaseWidget.cpp \
- $(srcdir)/ui/video/VideoMediaPlayerWidget.cpp \
- $(srcdir)/ui/video/VideoParametersDialog.cpp \
$(srcdir)/ui/settings/Settings.cpp \
$(srcdir)/ui/settings/SettingsAccountFormView.cpp \
$(srcdir)/ui/settings/SettingsAudioFormView.cpp \
@@ -63,6 +51,14 @@ lms_SOURCES = \
$(srcdir)/ui/settings/SettingsUserFormView.cpp \
$(srcdir)/ui/settings/SettingsUsers.cpp
+if VIDEO
+lms_SOURCES += \
+ $(srcdir)/ui/video/VideoWidget.cpp \
+ $(srcdir)/ui/video/VideoDatabaseWidget.cpp \
+ $(srcdir)/ui/video/VideoMediaPlayerWidget.cpp \
+ $(srcdir)/ui/video/VideoParametersDialog.cpp
+endif
+
if LMSAPI
lms_SOURCES += \
$(srcdir)/lms-api/server/Connection.cpp \
diff --git a/src/av/AvInfo.cpp b/src/av/AvInfo.cpp
new file mode 100644
index 00000000..23902cd4
--- /dev/null
+++ b/src/av/AvInfo.cpp
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2015 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 .
+ */
+
+#include
+
+#include "logger/Logger.hpp"
+
+#include "AvInfo.hpp"
+
+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};
+
+ if (av_strerror(error, buf.data(), buf.size()) == 0)
+ return std::string(&buf[0]);
+ else
+ return "Unknown error";
+}
+
+
+void AvInit()
+{
+ /* register all the codecs */
+ avcodec_register_all();
+ av_register_all();
+ LMS_LOG(MOD_AV, SEV_INFO) << "avcodec version = " << avcodec_version();
+}
+
+
+
+
+MediaFile::MediaFile(const boost::filesystem::path& p)
+: _p(p), _context(nullptr)
+{
+}
+
+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(MOD_AV, SEV_ERROR) << "Cannot open '" << _p.string() << "', avformat_open_input returned " << 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(MOD_AV, SEV_ERROR) << "Cannot find stream information: '" << averror_to_string(error);
+ return false;
+ }
+
+ return true;
+}
+
+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
+ return boost::posix_time::seconds(0); // TODO, do something better?
+}
+
+void
+getMetaDataFromDictionnary(AVDictionary* dictionnary, std::map& res)
+{
+ if (!dictionnary)
+ return;
+
+ AVDictionaryEntry *tag = NULL;
+ while ((tag = av_dict_get(dictionnary, "", tag, AV_DICT_IGNORE_SUFFIX)))
+ {
+ res.insert( std::make_pair(tag->key, tag->value));
+ }
+}
+
+std::map
+MediaFile::getMetaData(void)
+{
+ if (_context == nullptr)
+ throw std::logic_error("inputfile '" + _p.string() + "' not open");
+
+ std::map res;
+
+ getMetaDataFromDictionnary(_context->metadata, res);
+
+ // HACK for OGG files
+ // If we did not find tags, search metadata in streams
+ if (res.empty())
+ {
+ for (std::size_t i = 0; i < _context->nb_streams; ++i)
+ {
+ getMetaDataFromDictionnary(_context->streams[i]->metadata, res);
+
+ if (!res.empty())
+ break;
+ }
+ }
+
+ return res;
+}
+
+std::vector
+MediaFile::getStreams(Stream::Type type) const
+{
+ if (_context == nullptr)
+ throw std::logic_error("inputfile '" + _p.string() + "' not open");
+
+ std::vector res;
+
+ for (std::size_t i = 0; i < _context->nb_streams; ++i)
+ {
+ AVStream* avstream = _context->streams[i];
+
+ // Skip attached pics
+ if (avstream->disposition & AV_DISPOSITION_ATTACHED_PIC)
+ continue;
+
+ if (avstream->codec == nullptr)
+ {
+ LMS_LOG(MOD_AV, SEV_ERROR) << "Skipping stream " << i << " since no codec 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)
+ 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);
+ }
+
+ return res;
+}
+
+int
+MediaFile::getBestStreamId(Stream::Type type) 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 -1;
+ }
+
+ int res = av_find_best_stream(_context,
+ avMediaType,
+ -1, // Auto
+ -1, // Auto
+ NULL,
+ 0);
+ if (res < 0)
+ {
+ LMS_LOG(MOD_AV, SEV_ERROR) << "Cannot find best stream for type " << streamType_to_string(type);
+ return -1;
+ }
+
+ return res;
+}
+
+bool
+MediaFile::hasAttachedPictures(void) const
+{
+ for (std::size_t i = 0; i < _context->nb_streams; ++i)
+ {
+ if (_context->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC)
+ return true;
+ }
+
+ return false;
+}
+
+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" },
+ { AV_CODEC_ID_GIF, "image/gif" },
+ { AV_CODEC_ID_MJPEG, "image/jpeg" },
+ { AV_CODEC_ID_PNG, "image/png" },
+ { AV_CODEC_ID_PNG, "image/x-png" },
+ { AV_CODEC_ID_PPM, "image/x-portable-pixmap" },
+ };
+
+ std::vector pictures;
+
+ for (std::size_t i = 0; i < _context->nb_streams; ++i)
+ {
+ AVStream *avstream = _context->streams[i];
+
+ // Skip attached pics
+ if (!(avstream->disposition & AV_DISPOSITION_ATTACHED_PIC))
+ continue;
+
+ if (avstream->codec == nullptr)
+ {
+ LMS_LOG(MOD_AV, SEV_ERROR) << "Skipping stream " << i << " since no codec is set";
+ continue;
+ }
+
+ Picture picture;
+
+ auto itMime = codecMimeMap.find(avstream->codec->codec_id);
+ if (itMime != codecMimeMap.end())
+ {
+ picture.mimeType = itMime->second;
+ }
+ else
+ {
+ picture.mimeType = "application/octet-stream";
+ LMS_LOG(MOD_AV, SEV_ERROR) << "CODEC ID " << avstream->codec->codec_id << " not handled in mime type conversion";
+ }
+
+ AVPacket pkt = avstream->attached_pic;
+
+ std::copy(pkt.data, pkt.data + pkt.size, std::back_inserter(picture.data));
+
+ pictures.push_back( picture );
+
+ if (pictures.size() >= nbMaxPictures)
+ break;
+ }
+
+ return pictures;
+}
+
+} // namespace Av
diff --git a/src/av/AvInfo.hpp b/src/av/AvInfo.hpp
new file mode 100644
index 00000000..7d8061a2
--- /dev/null
+++ b/src/av/AvInfo.hpp
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2015 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 .
+ */
+
+/* This file contains some classes in order to get info from file using the libavconv */
+
+#ifndef AV_INFO_HPP
+#define AV_INFO_HPP
+
+extern "C"
+{
+#define __STDC_CONSTANT_MACROS
+#include
+#include
+#include
+}
+
+#include
+#include
+#include
+#include