Use ffmpeg for thumbnail support.
Removed dep on QtGui for the headless modules.
This commit is contained in:
@@ -3,7 +3,8 @@ include(../common/defines.pri)
|
||||
|
||||
TARGET = qcma_cli
|
||||
TEMPLATE += app
|
||||
QT += network sql
|
||||
QT += network sql core
|
||||
QT -= gui
|
||||
LIBS += -L../common -lqcma_common
|
||||
|
||||
SOURCES += \
|
||||
|
@@ -22,6 +22,7 @@
|
||||
#include "database.h"
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QImage>
|
||||
#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)
|
||||
{
|
||||
#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 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
return data;
|
||||
}
|
||||
@@ -201,29 +251,27 @@ QByteArray AVDecoder::getVideoThumbnail(int width, int height)
|
||||
AVFrame *pFrameRGB = avcodec_alloc_frame();
|
||||
#endif
|
||||
|
||||
int numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
|
||||
uint8_t *buffer = (uint8_t *)av_malloc(numBytes);
|
||||
|
||||
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);
|
||||
if(pFrameRGB == NULL) {
|
||||
sws_freeContext(sws_ctx);
|
||||
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_ctx,
|
||||
pFrame->data,
|
||||
@@ -234,30 +282,63 @@ QByteArray AVDecoder::getVideoThumbnail(int width, int height)
|
||||
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++) {
|
||||
memcpy(image.scanLine(y), pFrameRGB->data[0] + y * pFrameRGB->linesize[0], w * 3);
|
||||
if(pOCodecCtx == NULL) {
|
||||
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);
|
||||
imgbuffer.open(QIODevice::WriteOnly);
|
||||
QImage result = image.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
result.save(&imgbuffer, "JPEG");
|
||||
pOCodecCtx->bit_rate = pCodecCtx->bit_rate;
|
||||
pOCodecCtx->width = width;
|
||||
pOCodecCtx->height = height;
|
||||
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);
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55,28,1)
|
||||
av_frame_free(&pFrameRGB);
|
||||
av_frame_free(&pFrame);
|
||||
#else
|
||||
av_free(pFrameRGB);
|
||||
av_free(pFrame);
|
||||
av_free(&pFrameRGB);
|
||||
#endif
|
||||
avcodec_close(pOCodecCtx);
|
||||
sws_freeContext(sws_ctx);
|
||||
|
||||
avcodec_close(pCodecCtx);
|
||||
|
||||
return data;
|
||||
return buffer2;
|
||||
}
|
||||
|
||||
void AVDecoder::close()
|
||||
|
@@ -42,9 +42,9 @@ public:
|
||||
bool open(const QString filename);
|
||||
void close();
|
||||
bool loadCodec(codec_type codec);
|
||||
bool seekVideo(int percentage);
|
||||
|
||||
QByteArray getAudioThumbnail(int width, int height);
|
||||
QByteArray getVideoThumbnail(int width, int height);
|
||||
QByteArray getThumbnail(int &width, int &height);
|
||||
void getAudioMetadata(metadata_t &metadata);
|
||||
void getVideoMetadata(metadata_t &metadata);
|
||||
const char *getMetadataEntry(const char *key, const char *default_value = NULL);
|
||||
@@ -80,6 +80,7 @@ public:
|
||||
|
||||
private:
|
||||
AVFrame *getDecodedFrame(AVCodecContext *pCodecCtx, int stream_index);
|
||||
QByteArray WriteJPEG(AVCodecContext *pCodecCtx, AVFrame *pFrame, int width, int height);
|
||||
|
||||
AVFormatContext *pFormatCtx;
|
||||
AVCodecContext *pCodecCtx;
|
||||
|
@@ -23,7 +23,6 @@
|
||||
#include <QBuffer>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QImage>
|
||||
#include <QSettings>
|
||||
|
||||
#ifdef Q_OS_WIN32
|
||||
@@ -112,14 +111,12 @@ static QByteArray findFolderAlbumArt(const QString path, metadata_t *metadata)
|
||||
foreach(const QString &ext, ext_list) {
|
||||
if(file.compare(QString("%1.%2").arg(cover, ext), Qt::CaseInsensitive) == 0) {
|
||||
qDebug() << "Trying to load album art from" << folder.absoluteFilePath(file);
|
||||
QImage img;
|
||||
if(img.load(folder.absoluteFilePath(file))) {
|
||||
QBuffer buffer(&data);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
QImage result = img.scaled(256, 250, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
result.save(&buffer, "JPEG");
|
||||
metadata->data.thumbnail.width = result.width();
|
||||
metadata->data.thumbnail.height = result.height();
|
||||
AVDecoder decoder;
|
||||
if(decoder.open(folder.absoluteFilePath(file))) {
|
||||
int width, height;
|
||||
data = decoder.getThumbnail(width, height);
|
||||
metadata->data.thumbnail.width = width;
|
||||
metadata->data.thumbnail.height = height;
|
||||
}
|
||||
// only try with the first match
|
||||
break;
|
||||
@@ -140,16 +137,14 @@ QByteArray getThumbnail(const QString &path, DataType type, metadata_t *metadata
|
||||
if(file.open(QIODevice::ReadOnly)) {
|
||||
data = file.readAll();
|
||||
}
|
||||
} else if(MASK_SET(type, Photo)) {
|
||||
QImage img;
|
||||
} else if(MASK_SET(type, Photo)) {
|
||||
AVDecoder decoder;
|
||||
|
||||
if(img.load(path)) {
|
||||
QBuffer buffer(&data);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
QImage result = img.scaled(240, 136, Qt::KeepAspectRatio, Qt::FastTransformation);
|
||||
result.save(&buffer, "JPEG");
|
||||
metadata->data.thumbnail.width = result.width();
|
||||
metadata->data.thumbnail.height = result.height();
|
||||
if(decoder.open(path)) {
|
||||
int width, height;
|
||||
data = decoder.getThumbnail(width, height);
|
||||
metadata->data.thumbnail.width = width;
|
||||
metadata->data.thumbnail.height = height;
|
||||
}
|
||||
} else if(MASK_SET(type, Music)) {
|
||||
if(MASK_SET(type, Folder)) {
|
||||
@@ -159,17 +154,23 @@ QByteArray getThumbnail(const QString &path, DataType type, metadata_t *metadata
|
||||
AVDecoder decoder;
|
||||
|
||||
if(decoder.open(path)) {
|
||||
data = decoder.getAudioThumbnail(256, 256);
|
||||
metadata->data.thumbnail.width = 256;
|
||||
metadata->data.thumbnail.height = 256;
|
||||
int width, height;
|
||||
data = decoder.getThumbnail(width, height);
|
||||
metadata->data.thumbnail.width = width;
|
||||
metadata->data.thumbnail.height = height;
|
||||
}
|
||||
}
|
||||
} else if(MASK_SET(type, Video)) {
|
||||
AVDecoder decoder;
|
||||
if(decoder.open(path)) {
|
||||
data = decoder.getVideoThumbnail(256, 256);
|
||||
metadata->data.thumbnail.width = 256;
|
||||
metadata->data.thumbnail.height = 256;
|
||||
int percentage = QSettings().value("videoThumbnailSeekPercentage", 30).toInt();
|
||||
decoder.seekVideo(percentage);
|
||||
|
||||
int width, height;
|
||||
|
||||
data = decoder.getThumbnail(width, height);
|
||||
metadata->data.thumbnail.width = width;
|
||||
metadata->data.thumbnail.height = height;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
|
@@ -21,7 +21,6 @@
|
||||
#include "avdecoder.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QImage>
|
||||
#include <QSettings>
|
||||
#include <QTextStream>
|
||||
#include <QThread>
|
||||
@@ -141,12 +140,13 @@ void Database::loadVideoMetadata(const QString &path, metadata_t &metadata)
|
||||
|
||||
void Database::loadPhotoMetadata(const QString &path, metadata_t &metadata)
|
||||
{
|
||||
QImage img;
|
||||
bool skipMetadata = QSettings().value("skipMetadata", false).toBool();
|
||||
//FIXME: use avdecoder
|
||||
//QImage img;
|
||||
//bool skipMetadata = QSettings().value("skipMetadata", false).toBool();
|
||||
|
||||
if(!skipMetadata && img.load(path)) {
|
||||
metadata.data.photo.tracks->data.track_photo.width = img.width();
|
||||
metadata.data.photo.tracks->data.track_photo.height = img.height();
|
||||
}
|
||||
//if(!skipMetadata && img.load(path)) {
|
||||
// metadata.data.photo.tracks->data.track_photo.width = img.width();
|
||||
// metadata.data.photo.tracks->data.track_photo.height = img.height();
|
||||
//}
|
||||
metadata.data.photo.title = strdup(metadata.name);
|
||||
}
|
||||
|
@@ -26,7 +26,6 @@
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QImage>
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
|
||||
#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)
|
||||
{
|
||||
int ohfi;
|
||||
QImage img;
|
||||
//QImage img;
|
||||
uint date_created;
|
||||
int width, height, file_format, photo_codec;
|
||||
|
||||
@@ -751,18 +750,18 @@ uint SQLiteDB::insertPhotoEntry(const QString &path, const QString &name, int id
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(!img.load(path + "/" + name)) {
|
||||
return 0;
|
||||
}
|
||||
//if(!img.load(path + "/" + name)) {
|
||||
// return 0;
|
||||
//}
|
||||
|
||||
QDateTime date = QFileInfo(path + "/" + name).created();
|
||||
date_created = date.toUTC().toTime_t();
|
||||
QString month_created = date.toString("yyyy/MM");
|
||||
|
||||
width = img.width();
|
||||
height = img.height();
|
||||
file_format = photo_list[cma_file_type].file_format;
|
||||
photo_codec = photo_list[cma_file_type].file_codec;
|
||||
width = 0; //img.width();
|
||||
height = 0; //img.height();
|
||||
file_format = 0; //photo_list[cma_file_type].file_format;
|
||||
photo_codec = 0; //photo_list[cma_file_type].file_codec;
|
||||
|
||||
QByteArray basename = QFileInfo(name).baseName().toUtf8();
|
||||
|
||||
|
Reference in New Issue
Block a user