diff --git a/cli/cli.pro b/cli/cli.pro index a37bfad..6a3869e 100644 --- a/cli/cli.pro +++ b/cli/cli.pro @@ -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 += \ diff --git a/common/avdecoder.cpp b/common/avdecoder.cpp index 74ec2d2..2fa3398 100644 --- a/common/avdecoder.cpp +++ b/common/avdecoder.cpp @@ -22,6 +22,7 @@ #include "database.h" #include +#include #include #include #include @@ -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(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() diff --git a/common/avdecoder.h b/common/avdecoder.h index 586dc98..9521e5d 100644 --- a/common/avdecoder.h +++ b/common/avdecoder.h @@ -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; diff --git a/common/cmautils.cpp b/common/cmautils.cpp index e7ab672..eca402c 100644 --- a/common/cmautils.cpp +++ b/common/cmautils.cpp @@ -23,7 +23,6 @@ #include #include #include -#include #include #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; diff --git a/common/database.cpp b/common/database.cpp index 896ee88..7a21280 100644 --- a/common/database.cpp +++ b/common/database.cpp @@ -21,7 +21,6 @@ #include "avdecoder.h" #include -#include #include #include #include @@ -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); } diff --git a/common/sqlitedb.cpp b/common/sqlitedb.cpp index fa874a7..90f08af 100644 --- a/common/sqlitedb.cpp +++ b/common/sqlitedb.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) #include @@ -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();