- Notifications
You must be signed in to change notification settings - Fork3.9k
Add snapshot filter mark#4571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
base:master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Pull request overview
This PR adds watermarking functionality to video frame snapshots by integrating FFmpeg's filter graph API to overlay text (specifically "ZLMediaKit") on saved JPEG/PNG images. The implementation extends thesaveFrame function with optional parameters for custom dimensions and font paths, and introduces a fallback mechanism for system fonts across different platforms.
Key Changes
- Adds AVFilter library integration with filter graph for text overlay on snapshot images
- Extends
saveFrameAPI with optional width, height, and font_path parameters for customizable output - Implements cross-platform font fallback logic (Linux, macOS, Windows)
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| src/Codec/Transcode.h | Adds AVFilter includes and extends saveFrame signature with w, h, and font_path parameters |
| src/Codec/Transcode.cpp | Implements filter graph setup, drawtext filter configuration, and font path resolution for watermarking |
| CMakeLists.txt | Adds AVFILTER library dependency detection and linking |
💡Add Copilot custom instructions for smarter, more guided reviews.Learn how to get started.
src/Codec/Transcode.h Outdated
| * @param filename 保存文件路径 | ||
| * @param fmt jpg:AV_PIX_FMT_YUVJ420P,PNG:AV_PIX_FMT_RGB24 | ||
| * @param w h 图片大小,默认和输入源一致 | ||
| * @param font_path |
CopilotAINov 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
The@param font_path documentation is incomplete. It should describe what the parameter is for (e.g., "Path to custom font file for watermark text. If not provided or invalid, falls back to system default fonts.").
| * @paramfont_path | |
| * @paramfont_pathPathtocustomfontfileforwatermarktext.Ifnotprovidedorinvalid,fallsbacktosystemdefaultfonts. |
| ss <<"Could not open the file" << filename; | ||
| buffersrc =avfilter_get_by_name("buffer"); | ||
| if (ret =avfilter_graph_create_filter(&buffersrc_ctx, buffersrc,"in", args,NULL, _filter_graph.get()) <0) { |
CopilotAINov 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Operator precedence error: The assignmentret = avfilter_graph_create_filter(...) < 0 evaluates asret = (avfilter_graph_create_filter(...) < 0), assigning a boolean toret instead of the function's return value. This breaks error reporting withffmpeg_err(ret) on line 853. Use parentheses:if ((ret = avfilter_graph_create_filter(...)) < 0).
src/Codec/Transcode.cpp Outdated
| AVFilterContext *drawtext_ctx1 =nullptr; | ||
| const AVFilter *drawtext_filter =avfilter_get_by_name("drawtext"); | ||
| if (ret =avfilter_graph_create_filter(&drawtext_ctx1, drawtext_filter,"drawtext", drawtext_args1,NULL, _filter_graph.get()) <0) { |
CopilotAINov 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Operator precedence error: The assignmentret = avfilter_graph_create_filter(...) < 0 evaluates asret = (avfilter_graph_create_filter(...) < 0), assigning a boolean toret instead of the function's return value. This breaks error reporting withffmpeg_err(ret) on line 868. Use parentheses:if ((ret = avfilter_graph_create_filter(...)) < 0).
| if (ret = avfilter_graph_create_filter(&drawtext_ctx1, drawtext_filter,"drawtext", drawtext_args1,NULL, _filter_graph.get()) <0) { | |
| if ((ret = avfilter_graph_create_filter(&drawtext_ctx1, drawtext_filter,"drawtext", drawtext_args1,NULL, _filter_graph.get())) <0) { |
src/Codec/Transcode.cpp Outdated
| return make_tuple<bool, std::string>(false, ss.data()); | ||
| } | ||
| if (ret =av_buffersrc_add_frame_flags(buffersrc_ctx, new_frame->get(),0) <0) { |
CopilotAINov 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Operator precedence error: The assignmentret = av_buffersrc_add_frame_flags(...) < 0 evaluates asret = (av_buffersrc_add_frame_flags(...) < 0), assigning a boolean toret instead of the function's return value. This breaks error reporting withffmpeg_err(ret) on line 883. Use parentheses:if ((ret = av_buffersrc_add_frame_flags(...)) < 0).
| if (ret = av_buffersrc_add_frame_flags(buffersrc_ctx, new_frame->get(), 0) < 0) { | |
| if ((ret = av_buffersrc_add_frame_flags(buffersrc_ctx, new_frame->get(), 0)) < 0) { |
| while (avcodec_receive_packet(jpeg_codec_ctx.get(), pkt.get()) ==0) { | ||
| fwrite(pkt.get()->data, pkt.get()->size,1, tmp_save_file_jpg.get()); | ||
| buffersink =avfilter_get_by_name("buffersink"); | ||
| if (ret =avfilter_graph_create_filter(&buffersink_ctx, buffersink,"out",NULL,NULL, _filter_graph.get()) <0) { |
CopilotAINov 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Operator precedence error: The assignmentret = avfilter_graph_create_filter(...) < 0 evaluates asret = (avfilter_graph_create_filter(...) < 0), assigning a boolean toret instead of the function's return value. This breaks error reporting withffmpeg_err(ret) on line 860. Use parentheses:if ((ret = avfilter_graph_create_filter(...)) < 0).
| if (ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink,"out",NULL,NULL, _filter_graph.get()) <0) { | |
| if ((ret = avfilter_graph_create_filter(&buffersink_ctx, buffersink,"out",NULL,NULL, _filter_graph.get())) <0) { |
src/Codec/Transcode.cpp Outdated
| return make_tuple<bool, std::string>(false, ss.data()); | ||
| } | ||
| if (ret =avfilter_link(buffersrc_ctx,0, drawtext_ctx1,0) <0 ||avfilter_link(drawtext_ctx1,0, buffersink_ctx,0) <0) { |
CopilotAINov 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Operator precedence error and incorrect error tracking: The first assignmentret = avfilter_link(...) < 0 evaluates asret = (avfilter_link(...) < 0), assigning a boolean. Additionally, if the secondavfilter_link fails,ret won't contain its error code. Split into separate conditions or use:if ((ret = avfilter_link(buffersrc_ctx, 0, drawtext_ctx1, 0)) < 0 || (ret = avfilter_link(drawtext_ctx1, 0, buffersink_ctx, 0)) < 0).
| if (ret = avfilter_link(buffersrc_ctx,0, drawtext_ctx1,0) <0 || avfilter_link(drawtext_ctx1,0, buffersink_ctx,0) <0) { | |
| if ((ret = avfilter_link(buffersrc_ctx,0, drawtext_ctx1,0)) <0 ||(ret =avfilter_link(drawtext_ctx1,0, buffersink_ctx,0)) <0) { |
src/Codec/Transcode.cpp Outdated
| return make_tuple<bool, std::string>(false, ss.data()); | ||
| } | ||
| if (ret =avfilter_graph_config(_filter_graph.get(),NULL) <0) { |
CopilotAINov 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Operator precedence error: The assignmentret = avfilter_graph_config(...) < 0 evaluates asret = (avfilter_graph_config(...) < 0), assigning a boolean toret instead of the function's return value. This breaks error reporting withffmpeg_err(ret) on line 878. Use parentheses:if ((ret = avfilter_graph_config(...)) < 0).
| if (ret = avfilter_graph_config(_filter_graph.get(),NULL) <0) { | |
| if ((ret = avfilter_graph_config(_filter_graph.get(),NULL)) <0) { |
| } | ||
| if (ret =av_buffersrc_add_frame_flags(buffersrc_ctx, new_frame->get(),0) <0) { | ||
| ss <<"av_buffersink_get_frame failed:" << ret <<"" <<ffmpeg_err(ret); |
CopilotAINov 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Incorrect error message: The error says "av_buffersink_get_frame failed" but the actual function being called isav_buffersrc_add_frame_flags. The message should be "av_buffersrc_add_frame_flags failed".
| ss <<"av_buffersink_get_frame failed:" << ret <<"" << ffmpeg_err(ret); | |
| ss <<"av_buffersrc_add_frame_flags failed:" << ret <<"" << ffmpeg_err(ret); |
| while (av_buffersink_get_frame(buffersink_ctx, new_frame->get()) >=0) { | ||
| if (avcodec_send_frame(jpeg_codec_ctx.get(), new_frame->get()) ==0) { | ||
| while (avcodec_receive_packet(jpeg_codec_ctx.get(), pkt.get()) ==0) { | ||
| fwrite(pkt.get()->data, pkt.get()->size,1, tmp_save_file_jpg.get()); | ||
| } | ||
| } |
CopilotAINov 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Potential frame data corruption:new_frame is passed toav_buffersrc_add_frame_flags on line 882, then immediately reused on line 887 withav_buffersink_get_frame to retrieve the filtered frame. This overwrites the frame data. The filtered frame should be retrieved into a separate frame object to avoid data corruption.
| while (av_buffersink_get_frame(buffersink_ctx, new_frame->get()) >= 0) { | |
| if (avcodec_send_frame(jpeg_codec_ctx.get(), new_frame->get()) == 0) { | |
| while (avcodec_receive_packet(jpeg_codec_ctx.get(), pkt.get()) ==0) { | |
| fwrite(pkt.get()->data, pkt.get()->size, 1, tmp_save_file_jpg.get()); | |
| } | |
| } | |
| auto filtered_frame = std::make_shared<FFmpegFrame>(); | |
| while (av_buffersink_get_frame(buffersink_ctx, filtered_frame->get()) >= 0) { | |
| if (avcodec_send_frame(jpeg_codec_ctx.get(), filtered_frame->get()) == 0) { | |
| while (avcodec_receive_packet(jpeg_codec_ctx.get(), pkt.get()) ==0) { | |
| fwrite(pkt.get()->data, pkt.get()->size, 1, tmp_save_file_jpg.get()); | |
| } | |
| } | |
| filtered_frame->unref(); |
No description provided.