update ffmpeg calls to work with 4.0, but mess up the output framerate

This commit is contained in:
Rexy712 2019-03-08 15:14:51 -08:00
parent 1baa90fb3c
commit 0fb6e32fcb
7 changed files with 220 additions and 145 deletions

8
README.md Normal file
View File

@ -0,0 +1,8 @@
# README
## Dependencies
FreeImagePlus >= 3.18.0 for image upload support (earlier versions have a buffer overflow issue)
libav/ffmpeg >= 3.4.5 for video/audio upload support (not tested below 3.4.5)
libcurl for network communication

3
TODO
View File

@ -1,9 +1,10 @@
use libmagic to determine file types? use libmagic to determine file types?
add av_freep cleanup to raii interface for libav* (probably replacing unique_ptr in that case) add av_freep cleanup to raii interface for libav* (probably replacing unique_ptr in that case)
raii-ify the muxing function raii-ify the muxing function
fix muxing fps/bitrate issue
scale video thumbnails to same size as image thumbnails (500 in either dimension, same aspect ratio) scale video thumbnails to same size as image thumbnails (500 in either dimension, same aspect ratio)
crosspost title crosspost title
thread pool for workers. rehurn std::promise? thread pool for workers. rehurn std::promise?
sync with matrix. add events to a queue that consumer can wait on or nonblocking check sync with matrix. add events to a queue that consumer can wait on or nonblocking check
don't create thumbnail if image is already sufficiently small don't create thumbnail if image is already sufficiently small
youtube video download

View File

@ -62,6 +62,11 @@ namespace reddit{
class post class post
{ {
private:
enum post_flags{
POST_FLAGS_NONE = 0,
POST_FLAGS_CROSSPOSTED = 1
};
private: private:
raii::string m_post; raii::string m_post;
raii::rjp_string m_media_url; raii::rjp_string m_media_url;
@ -72,6 +77,7 @@ namespace reddit{
raii::rjp_string m_name; raii::rjp_string m_name;
raii::rjp_string m_post_url; raii::rjp_string m_post_url;
post_type m_type = post_type::unrecognized; post_type m_type = post_type::unrecognized;
int m_flags = POST_FLAGS_NONE;
public: public:
post(void) = default; post(void) = default;
@ -95,9 +101,11 @@ namespace reddit{
const raii::rjp_string& post_hint(void)const; const raii::rjp_string& post_hint(void)const;
const raii::rjp_string& title(void)const; const raii::rjp_string& title(void)const;
const raii::rjp_string& name(void)const; const raii::rjp_string& name(void)const;
bool is_crosspost(void)const;
post_type type(void)const; post_type type(void)const;
private: private:
void _parse_post(void); void _parse_post(void);
static post_type _handle_reddit_hosted_video(RJP_value* data, raii::rjp_string& media_url, raii::string& audio_url);
}; };
class bot class bot

View File

@ -22,7 +22,6 @@
#include <utility> //exchange, swap #include <utility> //exchange, swap
#include <cstdlib> //memcpy #include <cstdlib> //memcpy
#include <cstring> //strcpy, strlen #include <cstring> //strcpy, strlen
#include <new> //bad_alloc
namespace raii{ namespace raii{
string_base::string_base(size_t len): string_base::string_base(size_t len):
@ -35,8 +34,10 @@ namespace raii{
}else{ }else{
_free(m_data); _free(m_data);
m_data = _copy(c, len+1); m_data = _copy(c, len+1);
if(!m_data) if(!m_data){
throw std::bad_alloc{}; m_length = 0;
return *this;
}
} }
m_length = len; m_length = len;
return *this; return *this;
@ -47,8 +48,10 @@ namespace raii{
}else{ }else{
_free(m_data); _free(m_data);
m_data = _copy(s.m_data, s.m_length+1); m_data = _copy(s.m_data, s.m_length+1);
if(!m_data) if(!m_data){
throw std::bad_alloc{}; m_length = 0;
return *this;
}
} }
m_length = s.m_length; m_length = s.m_length;
return *this; return *this;

View File

@ -21,10 +21,17 @@
#include <utility> //exchange #include <utility> //exchange
//av_register_all is required before ffmpeg 4.0 and is deprecated after that
#if LIBAVFORMAT_VERSION_MAJOR >= 58 && LIBAVFORMAT_VERSION_MINOR >= 7
# define REGISTER_LIBAV()
#else
# define REGISTER_LIBAV() av_register_all()
#endif
namespace raii{ namespace raii{
video_man::video_man(const raii::string_base& filename){ video_man::video_man(const raii::string_base& filename){
av_register_all(); REGISTER_LIBAV();
_init(filename); _init(filename);
} }
video_man::video_man(video_man&& v): video_man::video_man(video_man&& v):

View File

@ -149,134 +149,6 @@ namespace reddit{
{ {
_parse_post(); _parse_post();
} }
void post::_parse_post(void){
raii::rjp_ptr root(rjp_parse(m_post));
if(!root)
return;
static const char* search_items[] = {"url", "author", "post_hint", "title", "id"};
static constexpr size_t num_searches = sizeof(search_items)/sizeof(search_items[0]);
RJP_search_res results[num_searches];
RJP_search_res data = rjp_search_member(root.get(), "data", 0);
if(!data.value) return;
data = rjp_search_member(data.value, "children", 0);
if(!data.value) return;
data.value = rjp_get_element(data.value);
if(!data.value) return;
RJP_search_res kind = rjp_search_member(data.value, "kind", 0);
if(!kind.value) return;
data = rjp_search_member(data.value, "data", 0);
if(!data.value) return;
RJP_search_res crosspost = rjp_search_member(data.value, "crosspost_parent_list", 0);
if(crosspost.value){
crosspost.value = rjp_get_element(crosspost.value);
if(crosspost.value)
data = crosspost;
}
rjp_search_members(data.value, num_searches, search_items, results, 0);
m_media_url = results[0].value;
m_author = results[1].value;
m_post_hint = results[2].value;
m_title = results[3].value;
m_name = raii::rjp_string(kind.value) + "_" + rjp_value_string(results[4].value);
m_post_url = "https://redd.it/" + raii::rjp_string(results[4].value);
if(m_post_hint){
//handle simple image
if(!strcmp(m_post_hint, "image")){
m_type = post_type::image;
}
//handle link
else if(!strcmp(m_post_hint, "link")){
m_type = post_type::link;
//imgur support
if(is_imgur_link(m_media_url)){
if(is_gifv(m_media_url)){
if(raii::rjp_string tmp = preview_search(data.value)){
m_media_url = std::move(tmp);
m_type = post_type::video;
}
}else{
m_media_url += ".jpg"; //imgur is dumb
m_type = post_type::image;
}
}else if(is_gfycat_link(m_media_url)){
if(raii::rjp_string tmp = find_video_url(data.value)){
m_media_url = std::move(tmp);
m_type = post_type::video;
}
}
}
//handle hosted video
else if(!strcmp(m_post_hint, "hosted:video")){
RJP_search_res media = rjp_search_member(data.value, "media", 0);
RJP_search_res gif = rjp_search_member(media.value, "reddit_video", 0);
if(gif.value)
gif = rjp_search_member(media.value, "is_gif", 0);
if(gif.value && rjp_value_boolean(gif.value)){
m_type = post_type::image;
}else{
raii::rjp_string res = media_search(media.value);
if(!res){
res = preview_search(data.value);
if(!res){
m_type = post_type::link;
return;
}
}
m_type = post_type::video;
m_media_url = std::move(res);
}
//reddit hosts audio and video separately. Meaning I have to find a way to manually recombine them
static constexpr char url_base[] = "https://v.redd.it/";
static constexpr size_t url_base_len = sizeof(url_base)-1;
char* end = strstr(m_media_url.get()+url_base_len, "/");
if(!end)
end = m_media_url.get()+m_media_url.length();
size_t len = end - m_media_url.get();
m_hosted_video_audio = raii::string(len + 6);
memcpy(m_hosted_video_audio.get(), m_media_url.get(), len);
memcpy(m_hosted_video_audio.get()+len, "/audio", 6);
m_hosted_video_audio[len+6] = 0;
}
else if(!strcmp(m_post_hint, "rich:video")){
RJP_search_res media = rjp_search_member(data.value, "media", 0);
raii::rjp_string res = media_search(media.value);
if(res){
m_type = post_type::video;
m_media_url = std::move(res);
return;
}
res = preview_search(data.value);
if(res){
m_type = post_type::video;
m_media_url = std::move(res);
return;
}
m_type = post_type::link;
}
//assume text post for other
else{
m_type = post_type::text;
}
}else if(is_direct_imgur_link(m_media_url)){
m_type = post_type::image;
return;
}else if(check_reddit_media_domain(data.value)){
RJP_value* media = rjp_search_member(data.value, "media", 0).value;
if(media && (rjp_value_type(media) != json_null))
m_type = post_type::video;
else
m_type = post_type::image;
}else{
m_media_url.reset();
m_type = post_type::text;
}
}
post& post::operator=(const raii::string_base& p){ post& post::operator=(const raii::string_base& p){
post tmp(p); post tmp(p);
if(!tmp) if(!tmp)
@ -314,9 +186,157 @@ namespace reddit{
const raii::rjp_string& post::name(void)const{ const raii::rjp_string& post::name(void)const{
return m_name; return m_name;
} }
bool post::is_crosspost(void)const{
return (m_flags & POST_FLAGS_CROSSPOSTED);
}
post_type post::type(void)const{ post_type post::type(void)const{
return m_type; return m_type;
} }
void post::_parse_post(void){
raii::rjp_ptr root(rjp_parse(m_post));
if(!root)
return;
static const char* search_items[] = {"url", "author", "post_hint", "title", "id", "crosspost_parent_list"};
static constexpr size_t num_searches = sizeof(search_items)/sizeof(search_items[0]);
RJP_search_res results[num_searches];
RJP_search_res data = rjp_search_member(root.get(), "data", 0);
if(!data.value) return;
data = rjp_search_member(data.value, "children", 0);
if(!data.value) return;
data.value = rjp_get_element(data.value);
if(!data.value) return;
RJP_search_res kind = rjp_search_member(data.value, "kind", 0);
if(!kind.value) return;
data = rjp_search_member(data.value, "data", 0);
if(!data.value) return;
RJP_search_res& crosspost = results[5];
rjp_search_members(data.value, num_searches, search_items, results, 0);
//reddit will *sometimes* make the url field point to the crosspost parent's comments page.
//so we just always assume that the true link is in the crosspost parent
if(crosspost.value){
m_flags |= POST_FLAGS_CROSSPOSTED;
crosspost.value = rjp_get_element(crosspost.value);
crosspost = rjp_search_member(crosspost.value, "url", 0);
if(crosspost.value)
m_media_url = crosspost.value;
}else{
m_media_url = results[0].value;
}
m_author = results[1].value;
m_post_hint = results[2].value;
m_title = results[3].value;
m_name = raii::rjp_string(kind.value) + "_" + rjp_value_string(results[4].value);
m_post_url = "https://redd.it/" + raii::rjp_string(results[4].value);
if(m_post_hint){
//handle simple image
if(!strcmp(m_post_hint, "image")){
m_type = post_type::image;
}
//handle link
else if(!strcmp(m_post_hint, "link")){
m_type = post_type::link;
//imgur support
if(is_imgur_link(m_media_url)){
if(is_gifv(m_media_url)){ //gifv is a video
if(raii::rjp_string tmp = preview_search(data.value)){
m_media_url = std::move(tmp);
m_type = post_type::video;
}
}else{
//imgur links don't lead to the image source. adding .jpg to the link leads to the source
//except when the link is to an album or to a gifv
m_media_url += ".jpg"; //imgur is dumb
m_type = post_type::image;
}
//gfycat support
}else if(is_gfycat_link(m_media_url)){
if(raii::rjp_string tmp = find_video_url(data.value)){
m_media_url = std::move(tmp);
m_type = post_type::video;
}
}
}
//handle hosted video
else if(!strcmp(m_post_hint, "hosted:video")){
m_type = _handle_reddit_hosted_video(data.value, m_media_url, m_hosted_video_audio);
}
else if(!strcmp(m_post_hint, "rich:video")){
RJP_search_res media = rjp_search_member(data.value, "media", 0);
raii::rjp_string res = media_search(media.value);
if(res){
m_type = post_type::video;
m_media_url = std::move(res);
return;
}
res = preview_search(data.value);
if(res){
m_type = post_type::video;
m_media_url = std::move(res);
return;
}
m_type = post_type::link;
}
else{
//assume text post for other
m_type = post_type::text;
}
}else if(is_direct_imgur_link(m_media_url)){
m_type = post_type::image;
return;
}else if(check_reddit_media_domain(data.value)){
m_type = _handle_reddit_hosted_video(data.value, m_media_url, m_hosted_video_audio);
/*RJP_value* media = rjp_search_member(data.value, "media", 0).value;
if(media && (rjp_value_type(media) != json_null))
m_type = post_type::video;
else
m_type = post_type::image;
//*/
}else{
m_media_url.reset();
m_type = post_type::text;
}
}
post_type post::_handle_reddit_hosted_video(RJP_value* data, raii::rjp_string& media_url, raii::string& audio_url){
RJP_search_res media = rjp_search_member(data, "media", 0);
RJP_search_res gif = rjp_search_member(media.value, "reddit_video", 0);
//treat gif as image even though reddit thinks they're videos
if(gif.value)
gif = rjp_search_member(media.value, "is_gif", 0);
if(gif.value && rjp_value_boolean(gif.value)){
return post_type::image;
}
raii::rjp_string res = media_search(media.value);
if(!res){
res = preview_search(data);
if(!res){
return post_type::link;
}
}
media_url = std::move(res);
//reddit hosts audio and video separately. Meaning I have to find a way to manually recombine them.
//this sets up a link to the audio source of the video. the video might not actually have audio. when downloading
//from the audio link, always make sure to check for 404 errors.
static constexpr char url_base[] = "https://v.redd.it/";
static constexpr size_t url_base_len = sizeof(url_base)-1;
char* end = strstr(media_url.get()+url_base_len, "/");
if(!end)
end = media_url.get()+media_url.length();
size_t len = end - media_url.get();
audio_url = raii::string(len + 6);
memcpy(audio_url.get(), media_url.get(), len);
memcpy(audio_url.get()+len, "/audio", 6);
audio_url[len+6] = 0;
return post_type::video;
}

View File

@ -45,11 +45,19 @@ extern "C"{
# include <libavutil/imgutils.h> //av_image_alloc # include <libavutil/imgutils.h> //av_image_alloc
} }
//av_register_all is required before ffmpeg 4.0 and is deprecated after that
#if LIBAVFORMAT_VERSION_MAJOR >= 58 && LIBAVFORMAT_VERSION_MINOR >= 7
# define REGISTER_LIBAV()
#else
# define REGISTER_LIBAV() av_register_all()
#endif
//a lot copied from a github repo, but with all deprecation warnings fixed. //a lot copied from a github repo, but with all deprecation warnings fixed.
//no idea how the one guy managed to figure out all this with the minimal and conflicting documentation for ffmpeg and libav //no idea how the one guy managed to figure out all this with the minimal and conflicting documentation for ffmpeg and libav
//with the new code that fixes invalid input pts/dts, now the output framerate/bitrate is off
bool mux_audio_video(const raii::string_base& audio_file, const raii::string_base& video_file, const raii::string_base& output_file){ bool mux_audio_video(const raii::string_base& audio_file, const raii::string_base& video_file, const raii::string_base& output_file){
av_register_all(); REGISTER_LIBAV();
av_log_set_level(AV_LOG_FATAL);
AVOutputFormat* out_format = NULL; AVOutputFormat* out_format = NULL;
AVFormatContext* audio_context = NULL, *video_context = NULL, *output_context = NULL; AVFormatContext* audio_context = NULL, *video_context = NULL, *output_context = NULL;
@ -91,7 +99,7 @@ bool mux_audio_video(const raii::string_base& audio_file, const raii::string_bas
AVStream* out_stream = avformat_new_stream(output_context, codec); AVStream* out_stream = avformat_new_stream(output_context, codec);
audio_index_out = out_stream->index; audio_index_out = out_stream->index;
if(output_context->oformat->flags & AVFMT_GLOBALHEADER){ if(output_context->oformat->flags & AVFMT_GLOBALHEADER){
tmp->flags |= CODEC_FLAG_GLOBAL_HEADER; tmp->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
} }
tmp->codec_tag = 0; tmp->codec_tag = 0;
avcodec_parameters_from_context(out_stream->codecpar, tmp); avcodec_parameters_from_context(out_stream->codecpar, tmp);
@ -111,7 +119,7 @@ bool mux_audio_video(const raii::string_base& audio_file, const raii::string_bas
AVStream* out_stream = avformat_new_stream(output_context, codec); AVStream* out_stream = avformat_new_stream(output_context, codec);
video_index_out = out_stream->index; video_index_out = out_stream->index;
if(output_context->oformat->flags & AVFMT_GLOBALHEADER){ if(output_context->oformat->flags & AVFMT_GLOBALHEADER){
tmp->flags |= CODEC_FLAG_GLOBAL_HEADER; tmp->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
} }
tmp->codec_tag = 0; tmp->codec_tag = 0;
avcodec_parameters_from_context(out_stream->codecpar, tmp); avcodec_parameters_from_context(out_stream->codecpar, tmp);
@ -141,9 +149,14 @@ bool mux_audio_video(const raii::string_base& audio_file, const raii::string_bas
} }
int64_t video_pts = 0, audio_pts = 0; int64_t video_pts = 0, audio_pts = 0;
AVPacket packet; int64_t last_video_dts = 0, last_audio_dts = 0;
while(true){ while(true){
AVPacket packet;
av_init_packet(&packet);
packet.data = NULL;
packet.size = 0;
int64_t* last_dts;
AVFormatContext* in_context; AVFormatContext* in_context;
int stream_index = 0; int stream_index = 0;
AVStream* in_stream, *out_stream; AVStream* in_stream, *out_stream;
@ -152,6 +165,7 @@ bool mux_audio_video(const raii::string_base& audio_file, const raii::string_bas
audio_pts, audio_context->streams[audio_index_in]->time_base) <= 0) audio_pts, audio_context->streams[audio_index_in]->time_base) <= 0)
{ {
//video //video
last_dts = &last_video_dts;
in_context = video_context; in_context = video_context;
stream_index = video_index_out; stream_index = video_index_out;
@ -168,6 +182,7 @@ bool mux_audio_video(const raii::string_base& audio_file, const raii::string_bas
} }
}else{ }else{
//audio //audio
last_dts = &last_audio_dts;
in_context = audio_context; in_context = audio_context;
stream_index = audio_index_out; stream_index = audio_index_out;
@ -186,9 +201,20 @@ bool mux_audio_video(const raii::string_base& audio_file, const raii::string_bas
in_stream = in_context->streams[packet.stream_index]; in_stream = in_context->streams[packet.stream_index];
out_stream = output_context->streams[stream_index]; out_stream = output_context->streams[stream_index];
packet.pts = av_rescale_q_rnd(packet.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX)); av_packet_rescale_ts(&packet, in_stream->time_base, out_stream->time_base);
packet.dts = av_rescale_q_rnd(packet.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
packet.duration = av_rescale_q(packet.duration, in_stream->time_base, out_stream->time_base); if(packet.dts < (*last_dts + !(output_context->oformat->flags & AVFMT_TS_NONSTRICT)) && packet.dts != AV_NOPTS_VALUE && (*last_dts) != AV_NOPTS_VALUE){
int64_t next_dts = (*last_dts)+1;
if(packet.pts >= packet.dts && packet.pts != AV_NOPTS_VALUE){
packet.pts = FFMAX(packet.pts, next_dts);
}
if(packet.pts == AV_NOPTS_VALUE){
packet.pts = next_dts;
}
packet.dts = next_dts;
}
(*last_dts) = packet.dts;
packet.pos = -1; packet.pos = -1;
packet.stream_index = stream_index; packet.stream_index = stream_index;
@ -296,11 +322,13 @@ int main(){
DEBUG_PRINT("matrix bot initialized\n"); DEBUG_PRINT("matrix bot initialized\n");
reddit::post reply; reddit::post reply;
reply = mybot.get_top_post("WTF"_ss, reply.name(), reddit::time::hour);
reply = mybot.get_top_post("WTF"_ss, reply.name(), reddit::time::hour);
{ {
int retries = 5; int retries = 5;
do{ do{
reply = mybot.get_top_post("ProgrammerHumor"_ss, reply.name(), reddit::time::hour); reply = mybot.get_top_post("WTF"_ss, reply.name(), reddit::time::hour);
if(reply.type() != reddit::post_type::text && reply.type() != reddit::post_type::link) if(reply.type() != reddit::post_type::text && reply.type() != reddit::post_type::link)
break; break;
--retries; --retries;
@ -348,8 +376,8 @@ int main(){
}else{ }else{
DEBUG_PRINT("Remuxing audio and video\n"); DEBUG_PRINT("Remuxing audio and video\n");
mux_audio_video("audio"_ss, "video"_ss, "testout"_ss); mux_audio_video("audio"_ss, "video"_ss, "testout"_ss);
remove("video");
remove("audio"); remove("audio");
remove("video");
} }
}else{ }else{
file_output_curl(curl, target, reply.mediaurl()); file_output_curl(curl, target, reply.mediaurl());