From 611940805eccff5d5e91ef372bd43c6b63cac7e4 Mon Sep 17 00:00:00 2001 From: Aras Pranckevicius Date: Wed, 27 Nov 2024 17:08:37 +0100 Subject: [PATCH] Fix #107493, #98480: video rotation metadata is not read Videos files recorded on most phones were coming in sideways; files recorded on some laptop cameras were coming in upside down. In both cases the display_matrix metadata of the video stream was not applied. This required changing internal container format of movie proxies to be MP4 instead of AVI, since AVI does not support rotation metadata. File extension kept at ".avi" to not disturb existing expectations. It might be better to "bake" rotation into proxies while building them, but right now we do not have a trivial way of doing that. Note that this is a potential behavior change; if someone already had manually rotated video strips, they would have to undo that rotation now. Pull Request: https://projects.blender.org/blender/blender/pulls/130455 --- intern/ffmpeg/ffmpeg_compat.h | 39 +++++++++++++++++++++++ source/blender/imbuf/intern/IMB_anim.hh | 1 + source/blender/imbuf/intern/anim_movie.cc | 11 +++++-- source/blender/imbuf/intern/indexer.cc | 7 +++- 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/intern/ffmpeg/ffmpeg_compat.h b/intern/ffmpeg/ffmpeg_compat.h index 482ec1e6a3d..4d5b30eef37 100644 --- a/intern/ffmpeg/ffmpeg_compat.h +++ b/intern/ffmpeg/ffmpeg_compat.h @@ -17,6 +17,7 @@ #include #include #include +#include #include /* Check if our ffmpeg is new enough, avoids user complaints. @@ -168,6 +169,44 @@ FFMPEG_INLINE size_t ffmpeg_get_buffer_alignment() return align; } +FFMPEG_INLINE void ffmpeg_copy_display_matrix(const AVStream *src, AVStream *dst) +{ +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(60, 29, 100) + const AVPacketSideData *src_matrix = av_packet_side_data_get(src->codecpar->coded_side_data, + src->codecpar->nb_coded_side_data, + AV_PKT_DATA_DISPLAYMATRIX); + if (src_matrix != nullptr) { + uint8_t *dst_matrix = (uint8_t *)av_memdup(src_matrix->data, src_matrix->size); + av_packet_side_data_add(&dst->codecpar->coded_side_data, + &dst->codecpar->nb_coded_side_data, + AV_PKT_DATA_DISPLAYMATRIX, + dst_matrix, + src_matrix->size, + 0); + } +#endif +} + +FFMPEG_INLINE int ffmpeg_get_video_rotation(const AVStream *stream) +{ +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(60, 29, 100) + const AVPacketSideData *src_matrix = av_packet_side_data_get( + stream->codecpar->coded_side_data, + stream->codecpar->nb_coded_side_data, + AV_PKT_DATA_DISPLAYMATRIX); + if (src_matrix != nullptr) { + /* ffmpeg reports rotation in [-180..+180] range; our image rotation + * uses different direction and [0..360] range. */ + double theta = -av_display_rotation_get((const int32_t *)src_matrix->data); + if (theta < 0.0) { + theta += 360.0; + } + return int(theta); + } +#endif + return 0; +} + /* -------------------------------------------------------------------- */ /** \name Deinterlace code block * diff --git a/source/blender/imbuf/intern/IMB_anim.hh b/source/blender/imbuf/intern/IMB_anim.hh index 7419256d6e2..48fff879974 100644 --- a/source/blender/imbuf/intern/IMB_anim.hh +++ b/source/blender/imbuf/intern/IMB_anim.hh @@ -34,6 +34,7 @@ struct ImBufAnim { double frs_sec_base; double start_offset; int x, y; + int video_rotation; /* for number */ char filepath[1024]; diff --git a/source/blender/imbuf/intern/anim_movie.cc b/source/blender/imbuf/intern/anim_movie.cc index d8a8af75db3..125713a7652 100644 --- a/source/blender/imbuf/intern/anim_movie.cc +++ b/source/blender/imbuf/intern/anim_movie.cc @@ -356,6 +356,8 @@ static int startffmpeg(ImBufAnim *anim) anim->x = pCodecCtx->width; anim->y = pCodecCtx->height; + anim->video_rotation = ffmpeg_get_video_rotation(video_stream); + /* Decode >8bit videos into floating point image. */ anim->is_float = calc_pix_fmt_max_component_bits(pCodecCtx->pix_fmt) > 8; @@ -652,6 +654,11 @@ static void ffmpeg_postprocess(ImBufAnim *anim, AVFrame *input, ImBuf *ibuf) if (filter_y) { IMB_filtery(ibuf); } + + /* Rotate video if display matrix is multiple of 90 degrees. */ + if (ELEM(anim->video_rotation, 90, 180, 270)) { + IMB_rotate_orthogonal(ibuf, anim->video_rotation); + } } static void final_frame_log(ImBufAnim *anim, @@ -1355,10 +1362,10 @@ bool IMB_anim_get_fps(const ImBufAnim *anim, int IMB_anim_get_image_width(ImBufAnim *anim) { - return anim->x; + return ELEM(anim->video_rotation, 90, 270) ? anim->y : anim->x; } int IMB_anim_get_image_height(ImBufAnim *anim) { - return anim->y; + return ELEM(anim->video_rotation, 90, 270) ? anim->x : anim->y; } diff --git a/source/blender/imbuf/intern/indexer.cc b/source/blender/imbuf/intern/indexer.cc index 30416de47bd..a3c4ea165d1 100644 --- a/source/blender/imbuf/intern/indexer.cc +++ b/source/blender/imbuf/intern/indexer.cc @@ -470,7 +470,10 @@ static proxy_output_ctx *alloc_proxy_output_ffmpeg(ImBufAnim *anim, } rv->of = avformat_alloc_context(); - rv->of->oformat = av_guess_format("avi", nullptr, nullptr); + /* Note: we keep on using .avi extension for proxies, + * but actual container can not be AVI, since it does not support + * video rotation metadata. */ + rv->of->oformat = av_guess_format("mp4", nullptr, nullptr); rv->of->url = av_strdup(filepath); @@ -551,6 +554,8 @@ static proxy_output_ctx *alloc_proxy_output_ffmpeg(ImBufAnim *anim, avcodec_parameters_from_context(rv->st->codecpar, rv->c); + ffmpeg_copy_display_matrix(st, rv->st); + int ret = avio_open(&rv->of->pb, filepath, AVIO_FLAG_WRITE); if (ret < 0) {