Use ffmpeg for thumbnail support.

Removed dep on QtGui for the headless modules.
This commit is contained in:
codestation
2015-05-26 23:34:13 -04:30
parent babe16c43d
commit b6eebd79b1
6 changed files with 192 additions and 109 deletions

View File

@@ -3,7 +3,8 @@ include(../common/defines.pri)
TARGET = qcma_cli TARGET = qcma_cli
TEMPLATE += app TEMPLATE += app
QT += network sql QT += network sql core
QT -= gui
LIBS += -L../common -lqcma_common LIBS += -L../common -lqcma_common
SOURCES += \ SOURCES += \

View File

@@ -22,6 +22,7 @@
#include "database.h" #include "database.h"
#include <QBuffer> #include <QBuffer>
#include <QDebug>
#include <QFile> #include <QFile>
#include <QImage> #include <QImage>
#include <QSettings> #include <QSettings>
@@ -120,27 +121,6 @@ void AVDecoder::getVideoMetadata(metadata_t &metadata)
} }
} }
QByteArray AVDecoder::getAudioThumbnail(int width, int height)
{
QByteArray data;
if(!loadCodec(CODEC_VIDEO)) {
return data;
}
AVPacket pkt;
if(av_read_frame(pFormatCtx, &pkt) >= 0) {
// first frame == first thumbnail (hopefully)
QBuffer imgbuffer(&data);
imgbuffer.open(QIODevice::WriteOnly);
QImage img = QImage::fromData(QByteArray((const char *)pkt.data, pkt.size));
QImage result = img.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
result.save(&imgbuffer, "JPEG");
}
return data;
}
AVFrame *AVDecoder::getDecodedFrame(AVCodecContext *codec_ctx, int frame_stream_index) AVFrame *AVDecoder::getDecodedFrame(AVCodecContext *codec_ctx, int frame_stream_index)
{ {
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1) #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1)
@@ -171,26 +151,96 @@ AVFrame *AVDecoder::getDecodedFrame(AVCodecContext *codec_ctx, int frame_stream_
} }
} }
QByteArray AVDecoder::getVideoThumbnail(int width, int height)
bool AVDecoder::seekVideo(int percentage)
{ {
QByteArray data;
AVFrame *pFrame;
int percentage = QSettings().value("videoThumbnailSeekPercentage", 30).toInt();
if(!loadCodec(CODEC_VIDEO)) {
return data;
}
qint64 seek_pos = pFormatCtx->duration * percentage / (AV_TIME_BASE * 100); qint64 seek_pos = pFormatCtx->duration * percentage / (AV_TIME_BASE * 100);
qint64 frame = av_rescale(seek_pos, av_stream->time_base.den, av_stream->time_base.num); qint64 frame = av_rescale(seek_pos, av_stream->time_base.den, av_stream->time_base.num);
if(avformat_seek_file(pFormatCtx, stream_index, 0, frame, frame, AVSEEK_FLAG_FRAME) < 0) { if(avformat_seek_file(pFormatCtx, stream_index, 0, frame, frame, AVSEEK_FLAG_FRAME) < 0) {
avcodec_close(pCodecCtx); avcodec_close(pCodecCtx);
return false;
}
return true;
}
static void calculate_thumbnail_dimensions(int src_width, int src_height,
int src_sar_num, int src_sar_den,
int &dst_width, int &dst_height) {
if ((src_sar_num <= 0) || (src_sar_den <= 0)) {
src_sar_num = 1;
src_sar_den = 1;
}
if ((src_width * src_sar_num) / src_sar_den > src_height) {
dst_width = 256;
dst_height = (dst_width * src_height) / ((src_width * src_sar_num) / src_sar_den);
} else {
dst_height = 256;
dst_width = (dst_height * ((src_width * src_sar_num) / src_sar_den)) / src_height;
}
if (dst_width < 8)
dst_width = 8;
if (dst_height < 1)
dst_height = 1;
}
QByteArray AVDecoder::getThumbnail(int &width, int &height)
{
QByteArray data;
if(!loadCodec(CODEC_VIDEO)) {
return data; return data;
} }
if((pFrame = getDecodedFrame(pCodecCtx, stream_index)) == NULL) { AVFrame *pFrame = getDecodedFrame(pCodecCtx, stream_index);
if(pFrame != NULL) {
calculate_thumbnail_dimensions(pCodecCtx->width, pCodecCtx->height,
pCodecCtx->sample_aspect_ratio.num,
pCodecCtx->sample_aspect_ratio.den,
width, height);
data = WriteJPEG(pCodecCtx, pFrame, width, height);
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1)
av_frame_free(&pFrame);
#else
av_free(pFrame);
#endif
}
avcodec_close(pCodecCtx);
return data;
}
QByteArray AVDecoder::WriteJPEG(AVCodecContext *pCodecCtx, AVFrame *pFrame, int width, int height)
{
AVCodecContext *pOCodecCtx;
AVCodec *pOCodec;
QByteArray data;
pOCodec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
if (!pOCodec) {
return data;
}
SwsContext *sws_ctx = sws_getContext(
pCodecCtx->width, pCodecCtx->height,
pCodecCtx->pix_fmt,
width, height,
PIX_FMT_YUVJ420P, SWS_BICUBIC,
NULL, NULL, NULL);
if(!sws_ctx) {
avcodec_close(pCodecCtx); avcodec_close(pCodecCtx);
return data; return data;
} }
@@ -201,29 +251,27 @@ QByteArray AVDecoder::getVideoThumbnail(int width, int height)
AVFrame *pFrameRGB = avcodec_alloc_frame(); AVFrame *pFrameRGB = avcodec_alloc_frame();
#endif #endif
int numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height); if(pFrameRGB == NULL) {
uint8_t *buffer = (uint8_t *)av_malloc(numBytes); sws_freeContext(sws_ctx);
avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
SwsContext *sws_ctx = sws_getContext(
pCodecCtx->width,
pCodecCtx->height,
pCodecCtx->pix_fmt,
pCodecCtx->width,
pCodecCtx->height,
PIX_FMT_RGB24,
SWS_BICUBIC,
NULL,
NULL,
NULL
);
if(!sws_ctx) {
avcodec_close(pCodecCtx);
return data; return data;
} }
int numBytes = avpicture_get_size(PIX_FMT_YUVJ420P, width, height);
uint8_t *buffer = (uint8_t *)av_malloc(numBytes);
if(!buffer) {
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1)
av_frame_free(&pFrameRGB);
#else
av_free(pFrameRGB);
#endif
sws_freeContext(sws_ctx);
return data;
}
avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_YUVJ420P, width, height);
sws_scale( sws_scale(
sws_ctx, sws_ctx,
pFrame->data, pFrame->data,
@@ -234,30 +282,63 @@ QByteArray AVDecoder::getVideoThumbnail(int width, int height)
pFrameRGB->linesize pFrameRGB->linesize
); );
QImage image(pCodecCtx->width, pCodecCtx->height, QImage::Format_RGB888); pOCodecCtx = avcodec_alloc_context3(pOCodec);
for(int y = 0, h = pCodecCtx->height, w = pCodecCtx->width; y < h; y++) { if(pOCodecCtx == NULL) {
memcpy(image.scanLine(y), pFrameRGB->data[0] + y * pFrameRGB->linesize[0], w * 3); av_free(buffer);
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1)
av_frame_free(&pFrameRGB);
#else
av_free(&pFrameRGB);
#endif
sws_freeContext(sws_ctx);
return 0;
} }
QBuffer imgbuffer(&data); pOCodecCtx->bit_rate = pCodecCtx->bit_rate;
imgbuffer.open(QIODevice::WriteOnly); pOCodecCtx->width = width;
QImage result = image.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation); pOCodecCtx->height = height;
result.save(&imgbuffer, "JPEG"); pOCodecCtx->pix_fmt = AV_PIX_FMT_YUVJ420P;
pOCodecCtx->codec_id = AV_CODEC_ID_MJPEG;
pOCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
pOCodecCtx->time_base.num = pCodecCtx->time_base.num;
pOCodecCtx->time_base.den = pCodecCtx->time_base.den;
AVDictionary *opts = NULL;
if(avcodec_open2(pOCodecCtx, pOCodec, &opts) < 0) {
return 0;
}
pOCodecCtx->mb_lmin = pOCodecCtx->lmin = pOCodecCtx->qmin * FF_QP2LAMBDA;
pOCodecCtx->mb_lmax = pOCodecCtx->lmax = pOCodecCtx->qmax * FF_QP2LAMBDA;
pOCodecCtx->flags = CODEC_FLAG_QSCALE;
pOCodecCtx->global_quality = pOCodecCtx->qmin * FF_QP2LAMBDA;
pFrame->pts = 1;
pFrame->quality = pOCodecCtx->global_quality;
AVPacket pkt;
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
int gotPacket;
avcodec_encode_video2(pOCodecCtx, &pkt, pFrameRGB, &gotPacket);
QByteArray buffer2(reinterpret_cast<char *>(pkt.data), pkt.size);
avcodec_close(pOCodecCtx);
av_free(buffer); av_free(buffer);
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1) #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1)
av_frame_free(&pFrameRGB); av_frame_free(&pFrameRGB);
av_frame_free(&pFrame);
#else #else
av_free(pFrameRGB); av_free(&pFrameRGB);
av_free(pFrame);
#endif #endif
avcodec_close(pOCodecCtx);
sws_freeContext(sws_ctx);
avcodec_close(pCodecCtx); return buffer2;
return data;
} }
void AVDecoder::close() void AVDecoder::close()

View File

@@ -42,9 +42,9 @@ public:
bool open(const QString filename); bool open(const QString filename);
void close(); void close();
bool loadCodec(codec_type codec); bool loadCodec(codec_type codec);
bool seekVideo(int percentage);
QByteArray getAudioThumbnail(int width, int height); QByteArray getThumbnail(int &width, int &height);
QByteArray getVideoThumbnail(int width, int height);
void getAudioMetadata(metadata_t &metadata); void getAudioMetadata(metadata_t &metadata);
void getVideoMetadata(metadata_t &metadata); void getVideoMetadata(metadata_t &metadata);
const char *getMetadataEntry(const char *key, const char *default_value = NULL); const char *getMetadataEntry(const char *key, const char *default_value = NULL);
@@ -80,6 +80,7 @@ public:
private: private:
AVFrame *getDecodedFrame(AVCodecContext *pCodecCtx, int stream_index); AVFrame *getDecodedFrame(AVCodecContext *pCodecCtx, int stream_index);
QByteArray WriteJPEG(AVCodecContext *pCodecCtx, AVFrame *pFrame, int width, int height);
AVFormatContext *pFormatCtx; AVFormatContext *pFormatCtx;
AVCodecContext *pCodecCtx; AVCodecContext *pCodecCtx;

View File

@@ -23,7 +23,6 @@
#include <QBuffer> #include <QBuffer>
#include <QDebug> #include <QDebug>
#include <QDir> #include <QDir>
#include <QImage>
#include <QSettings> #include <QSettings>
#ifdef Q_OS_WIN32 #ifdef Q_OS_WIN32
@@ -112,14 +111,12 @@ static QByteArray findFolderAlbumArt(const QString path, metadata_t *metadata)
foreach(const QString &ext, ext_list) { foreach(const QString &ext, ext_list) {
if(file.compare(QString("%1.%2").arg(cover, ext), Qt::CaseInsensitive) == 0) { if(file.compare(QString("%1.%2").arg(cover, ext), Qt::CaseInsensitive) == 0) {
qDebug() << "Trying to load album art from" << folder.absoluteFilePath(file); qDebug() << "Trying to load album art from" << folder.absoluteFilePath(file);
QImage img; AVDecoder decoder;
if(img.load(folder.absoluteFilePath(file))) { if(decoder.open(folder.absoluteFilePath(file))) {
QBuffer buffer(&data); int width, height;
buffer.open(QIODevice::WriteOnly); data = decoder.getThumbnail(width, height);
QImage result = img.scaled(256, 250, Qt::KeepAspectRatio, Qt::SmoothTransformation); metadata->data.thumbnail.width = width;
result.save(&buffer, "JPEG"); metadata->data.thumbnail.height = height;
metadata->data.thumbnail.width = result.width();
metadata->data.thumbnail.height = result.height();
} }
// only try with the first match // only try with the first match
break; break;
@@ -140,16 +137,14 @@ QByteArray getThumbnail(const QString &path, DataType type, metadata_t *metadata
if(file.open(QIODevice::ReadOnly)) { if(file.open(QIODevice::ReadOnly)) {
data = file.readAll(); data = file.readAll();
} }
} else if(MASK_SET(type, Photo)) { } else if(MASK_SET(type, Photo)) {
QImage img; AVDecoder decoder;
if(img.load(path)) { if(decoder.open(path)) {
QBuffer buffer(&data); int width, height;
buffer.open(QIODevice::WriteOnly); data = decoder.getThumbnail(width, height);
QImage result = img.scaled(240, 136, Qt::KeepAspectRatio, Qt::FastTransformation); metadata->data.thumbnail.width = width;
result.save(&buffer, "JPEG"); metadata->data.thumbnail.height = height;
metadata->data.thumbnail.width = result.width();
metadata->data.thumbnail.height = result.height();
} }
} else if(MASK_SET(type, Music)) { } else if(MASK_SET(type, Music)) {
if(MASK_SET(type, Folder)) { if(MASK_SET(type, Folder)) {
@@ -159,17 +154,23 @@ QByteArray getThumbnail(const QString &path, DataType type, metadata_t *metadata
AVDecoder decoder; AVDecoder decoder;
if(decoder.open(path)) { if(decoder.open(path)) {
data = decoder.getAudioThumbnail(256, 256); int width, height;
metadata->data.thumbnail.width = 256; data = decoder.getThumbnail(width, height);
metadata->data.thumbnail.height = 256; metadata->data.thumbnail.width = width;
metadata->data.thumbnail.height = height;
} }
} }
} else if(MASK_SET(type, Video)) { } else if(MASK_SET(type, Video)) {
AVDecoder decoder; AVDecoder decoder;
if(decoder.open(path)) { if(decoder.open(path)) {
data = decoder.getVideoThumbnail(256, 256); int percentage = QSettings().value("videoThumbnailSeekPercentage", 30).toInt();
metadata->data.thumbnail.width = 256; decoder.seekVideo(percentage);
metadata->data.thumbnail.height = 256;
int width, height;
data = decoder.getThumbnail(width, height);
metadata->data.thumbnail.width = width;
metadata->data.thumbnail.height = height;
} }
} }
return data; return data;

View File

@@ -21,7 +21,6 @@
#include "avdecoder.h" #include "avdecoder.h"
#include <QDebug> #include <QDebug>
#include <QImage>
#include <QSettings> #include <QSettings>
#include <QTextStream> #include <QTextStream>
#include <QThread> #include <QThread>
@@ -141,12 +140,13 @@ void Database::loadVideoMetadata(const QString &path, metadata_t &metadata)
void Database::loadPhotoMetadata(const QString &path, metadata_t &metadata) void Database::loadPhotoMetadata(const QString &path, metadata_t &metadata)
{ {
QImage img; //FIXME: use avdecoder
bool skipMetadata = QSettings().value("skipMetadata", false).toBool(); //QImage img;
//bool skipMetadata = QSettings().value("skipMetadata", false).toBool();
if(!skipMetadata && img.load(path)) { //if(!skipMetadata && img.load(path)) {
metadata.data.photo.tracks->data.track_photo.width = img.width(); // metadata.data.photo.tracks->data.track_photo.width = img.width();
metadata.data.photo.tracks->data.track_photo.height = img.height(); // metadata.data.photo.tracks->data.track_photo.height = img.height();
} //}
metadata.data.photo.title = strdup(metadata.name); metadata.data.photo.title = strdup(metadata.name);
} }

View File

@@ -26,7 +26,6 @@
#include <QDebug> #include <QDebug>
#include <QDir> #include <QDir>
#include <QFileInfo> #include <QFileInfo>
#include <QImage>
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#include <QStandardPaths> #include <QStandardPaths>
@@ -741,7 +740,7 @@ uint SQLiteDB::insertVideoEntry(const QString &path, const QString &name, int id
uint SQLiteDB::insertPhotoEntry(const QString &path, const QString &name, int id_parent, int type) uint SQLiteDB::insertPhotoEntry(const QString &path, const QString &name, int id_parent, int type)
{ {
int ohfi; int ohfi;
QImage img; //QImage img;
uint date_created; uint date_created;
int width, height, file_format, photo_codec; int width, height, file_format, photo_codec;
@@ -751,18 +750,18 @@ uint SQLiteDB::insertPhotoEntry(const QString &path, const QString &name, int id
return 0; return 0;
} }
if(!img.load(path + "/" + name)) { //if(!img.load(path + "/" + name)) {
return 0; // return 0;
} //}
QDateTime date = QFileInfo(path + "/" + name).created(); QDateTime date = QFileInfo(path + "/" + name).created();
date_created = date.toUTC().toTime_t(); date_created = date.toUTC().toTime_t();
QString month_created = date.toString("yyyy/MM"); QString month_created = date.toString("yyyy/MM");
width = img.width(); width = 0; //img.width();
height = img.height(); height = 0; //img.height();
file_format = photo_list[cma_file_type].file_format; file_format = 0; //photo_list[cma_file_type].file_format;
photo_codec = photo_list[cma_file_type].file_codec; photo_codec = 0; //photo_list[cma_file_type].file_codec;
QByteArray basename = QFileInfo(name).baseName().toUtf8(); QByteArray basename = QFileInfo(name).baseName().toUtf8();