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?
add av_freep cleanup to raii interface for libav* (probably replacing unique_ptr in that case)
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)
crosspost title
thread pool for workers. rehurn std::promise?
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
youtube video download

View File

@ -62,6 +62,11 @@ namespace reddit{
class post
{
private:
enum post_flags{
POST_FLAGS_NONE = 0,
POST_FLAGS_CROSSPOSTED = 1
};
private:
raii::string m_post;
raii::rjp_string m_media_url;
@ -72,6 +77,7 @@ namespace reddit{
raii::rjp_string m_name;
raii::rjp_string m_post_url;
post_type m_type = post_type::unrecognized;
int m_flags = POST_FLAGS_NONE;
public:
post(void) = default;
@ -95,9 +101,11 @@ namespace reddit{
const raii::rjp_string& post_hint(void)const;
const raii::rjp_string& title(void)const;
const raii::rjp_string& name(void)const;
bool is_crosspost(void)const;
post_type type(void)const;
private:
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

View File

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

View File

@ -21,10 +21,17 @@
#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{
video_man::video_man(const raii::string_base& filename){
av_register_all();
REGISTER_LIBAV();
_init(filename);
}
video_man::video_man(video_man&& v):

View File

@ -149,134 +149,6 @@ namespace reddit{
{
_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 tmp(p);
if(!tmp)
@ -314,9 +186,157 @@ namespace reddit{
const raii::rjp_string& post::name(void)const{
return m_name;
}
bool post::is_crosspost(void)const{
return (m_flags & POST_FLAGS_CROSSPOSTED);
}
post_type post::type(void)const{
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
}
//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.
//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){
av_register_all();
av_log_set_level(AV_LOG_FATAL);
REGISTER_LIBAV();
AVOutputFormat* out_format = 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);
audio_index_out = out_stream->index;
if(output_context->oformat->flags & AVFMT_GLOBALHEADER){
tmp->flags |= CODEC_FLAG_GLOBAL_HEADER;
tmp->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
tmp->codec_tag = 0;
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);
video_index_out = out_stream->index;
if(output_context->oformat->flags & AVFMT_GLOBALHEADER){
tmp->flags |= CODEC_FLAG_GLOBAL_HEADER;
tmp->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
tmp->codec_tag = 0;
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;
AVPacket packet;
int64_t last_video_dts = 0, last_audio_dts = 0;
while(true){
AVPacket packet;
av_init_packet(&packet);
packet.data = NULL;
packet.size = 0;
int64_t* last_dts;
AVFormatContext* in_context;
int stream_index = 0;
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)
{
//video
last_dts = &last_video_dts;
in_context = video_context;
stream_index = video_index_out;
@ -168,6 +182,7 @@ bool mux_audio_video(const raii::string_base& audio_file, const raii::string_bas
}
}else{
//audio
last_dts = &last_audio_dts;
in_context = audio_context;
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];
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));
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);
av_packet_rescale_ts(&packet, 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.stream_index = stream_index;
@ -296,11 +322,13 @@ int main(){
DEBUG_PRINT("matrix bot initialized\n");
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;
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)
break;
--retries;
@ -348,8 +376,8 @@ int main(){
}else{
DEBUG_PRINT("Remuxing audio and video\n");
mux_audio_video("audio"_ss, "video"_ss, "testout"_ss);
remove("video");
remove("audio");
remove("video");
}
}else{
file_output_curl(curl, target, reply.mediaurl());