From 56da5e3fb1579bdb973582516fd7eff7bbfcfe0c Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Tue, 31 May 2016 23:06:06 -0400 Subject: Initial commit --- .gitmodules | 3 + CMakeLists.txt | 22 +++ cmake/FindFFMPEG.cmake | 144 +++++++++++++++ sap.cpp | 473 +++++++++++++++++++++++++++++++++++++++++++++++++ vendor/rawr-ebooks | 1 + 5 files changed, 643 insertions(+) create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 cmake/FindFFMPEG.cmake create mode 100644 sap.cpp create mode 160000 vendor/rawr-ebooks diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a58f64c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vendor/rawr-ebooks"] + path = vendor/rawr-ebooks + url = https://github.com/hatkirby/rawr-ebooks diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..70a9572 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required (VERSION 3.1) +project (sap) + +set(CMAKE_BUILD_TYPE Debug) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") + +find_package(FFMPEG REQUIRED) +include_directories(${FFMPEG_INCLUDE_DIRS}) + +find_package(PkgConfig) +pkg_check_modules(GraphicsMagick GraphicsMagick++ REQUIRED) +include_directories(${GraphicsMagick_INCLUDE_DIRS}) +link_directories(${GraphicsMagick_LIBRARY_DIRS}) + +add_subdirectory(vendor/rawr-ebooks EXCLUDE_FROM_ALL) +include_directories(vendor/rawr-ebooks vendor/rawr-ebooks/vendor/libtwittercpp/src vendor/rawr-ebooks/vendor/yaml-cpp/include) + +add_executable(sap sap.cpp) +set_property(TARGET sap PROPERTY CXX_STANDARD 11) +set_property(TARGET sap PROPERTY CXX_STANDARD_REQUIRED ON) +target_link_libraries(sap rawr twitter++ ${FFMPEG_LIBRARIES} ${GraphicsMagick_LIBRARIES} yaml-cpp) diff --git a/cmake/FindFFMPEG.cmake b/cmake/FindFFMPEG.cmake new file mode 100644 index 0000000..d7f972e --- /dev/null +++ b/cmake/FindFFMPEG.cmake @@ -0,0 +1,144 @@ +# - Try to find FFMPEG +# Once done this will define +# FFMPEG_FOUND - System has FFMPEG +# FFMPEG_INCLUDE_DIRS - The FFMPEG include directories +# FFMPEG_LIBRARIES - The libraries needed to use FFMPEG +# FFMPEG_LIBRARY_DIRS - The directory to find FFMPEG libraries +# +# written by Roy Shilkrot 2013 http://www.morethantechnical.com/ +# + +find_package(PkgConfig) + + +MACRO(FFMPEG_FIND varname shortname headername) + + IF(NOT WIN32) + PKG_CHECK_MODULES(PC_${varname} ${shortname}) + + FIND_PATH(${varname}_INCLUDE_DIR "${shortname}/${headername}" + HINTS ${PC_${varname}_INCLUDEDIR} ${PC_${varname}_INCLUDE_DIRS} + NO_DEFAULT_PATH + ) + ELSE() + FIND_PATH(${varname}_INCLUDE_DIR "${shortname}/${headername}") + ENDIF() + + IF(${varname}_INCLUDE_DIR STREQUAL "${varname}_INCLUDE_DIR-NOTFOUND") + message(STATUS "look for newer strcture") + IF(NOT WIN32) + PKG_CHECK_MODULES(PC_${varname} "lib${shortname}") + + FIND_PATH(${varname}_INCLUDE_DIR "lib${shortname}/${headername}" + HINTS ${PC_${varname}_INCLUDEDIR} ${PC_${varname}_INCLUDE_DIRS} + NO_DEFAULT_PATH + ) + ELSE() + FIND_PATH(${varname}_INCLUDE_DIR "lib${shortname}/${headername}") + IF(${${varname}_INCLUDE_DIR} STREQUAL "${varname}_INCLUDE_DIR-NOTFOUND") + #Desperate times call for desperate measures + MESSAGE(STATUS "globbing...") + FILE(GLOB_RECURSE ${varname}_INCLUDE_DIR "/ffmpeg*/${headername}") + MESSAGE(STATUS "found: ${${varname}_INCLUDE_DIR}") + IF(${varname}_INCLUDE_DIR) + GET_FILENAME_COMPONENT(${varname}_INCLUDE_DIR "${${varname}_INCLUDE_DIR}" PATH) + GET_FILENAME_COMPONENT(${varname}_INCLUDE_DIR "${${varname}_INCLUDE_DIR}" PATH) + ELSE() + SET(${varname}_INCLUDE_DIR "${varname}_INCLUDE_DIR-NOTFOUND") + ENDIF() + ENDIF() + ENDIF() + ENDIF() + + + IF(${${varname}_INCLUDE_DIR} STREQUAL "${varname}_INCLUDE_DIR-NOTFOUND") + MESSAGE(STATUS "Can't find includes for ${shortname}...") + ELSE() + MESSAGE(STATUS "Found ${shortname} include dirs: ${${varname}_INCLUDE_DIR}") + +# GET_DIRECTORY_PROPERTY(FFMPEG_PARENT DIRECTORY ${${varname}_INCLUDE_DIR} PARENT_DIRECTORY) + GET_FILENAME_COMPONENT(FFMPEG_PARENT ${${varname}_INCLUDE_DIR} PATH) + MESSAGE(STATUS "Using FFMpeg dir parent as hint: ${FFMPEG_PARENT}") + + IF(NOT WIN32) + FIND_LIBRARY(${varname}_LIBRARIES NAMES ${shortname} + HINTS ${PC_${varname}_LIBDIR} ${PC_${varname}_LIBRARY_DIR} ${FFMPEG_PARENT}) + ELSE() +# FIND_PATH(${varname}_LIBRARIES "${shortname}.dll.a" HINTS ${FFMPEG_PARENT}) + FILE(GLOB_RECURSE ${varname}_LIBRARIES "${FFMPEG_PARENT}/*${shortname}.lib") + # GLOBing is very bad... but windows sux, this is the only thing that works + ENDIF() + + IF(${varname}_LIBRARIES STREQUAL "${varname}_LIBRARIES-NOTFOUND") + MESSAGE(STATUS "look for newer structure for library") + FIND_LIBRARY(${varname}_LIBRARIES NAMES lib${shortname} + HINTS ${PC_${varname}_LIBDIR} ${PC_${varname}_LIBRARY_DIR} ${FFMPEG_PARENT}) + ENDIF() + + + IF(${varname}_LIBRARIES STREQUAL "${varname}_LIBRARIES-NOTFOUND") + MESSAGE(STATUS "Can't find lib for ${shortname}...") + ELSE() + MESSAGE(STATUS "Found ${shortname} libs: ${${varname}_LIBRARIES}") + ENDIF() + + + IF(NOT ${varname}_INCLUDE_DIR STREQUAL "${varname}_INCLUDE_DIR-NOTFOUND" + AND NOT ${varname}_LIBRARIES STREQUAL ${varname}_LIBRARIES-NOTFOUND) + + MESSAGE(STATUS "found ${shortname}: include ${${varname}_INCLUDE_DIR} lib ${${varname}_LIBRARIES}") + SET(FFMPEG_${varname}_FOUND 1) + SET(FFMPEG_${varname}_INCLUDE_DIRS ${${varname}_INCLUDE_DIR}) + SET(FFMPEG_${varname}_LIBS ${${varname}_LIBRARIES}) + ELSE() + MESSAGE(STATUS "Can't find ${shortname}") + ENDIF() + + ENDIF() + +ENDMACRO(FFMPEG_FIND) + +FFMPEG_FIND(LIBAVFORMAT avformat avformat.h) +FFMPEG_FIND(LIBAVDEVICE avdevice avdevice.h) +FFMPEG_FIND(LIBAVCODEC avcodec avcodec.h) +FFMPEG_FIND(LIBAVUTIL avutil avutil.h) +FFMPEG_FIND(LIBSWSCALE swscale swscale.h) + +SET(FFMPEG_FOUND "NO") +IF (FFMPEG_LIBAVFORMAT_FOUND AND + FFMPEG_LIBAVDEVICE_FOUND AND + FFMPEG_LIBAVCODEC_FOUND AND + FFMPEG_LIBAVUTIL_FOUND AND + FFMPEG_LIBSWSCALE_FOUND + ) + + + SET(FFMPEG_FOUND "YES") + + SET(FFMPEG_INCLUDE_DIRS ${FFMPEG_LIBAVFORMAT_INCLUDE_DIRS}) + + SET(FFMPEG_LIBRARY_DIRS ${FFMPEG_LIBAVFORMAT_LIBRARY_DIRS}) + + SET(FFMPEG_LIBRARIES + ${FFMPEG_LIBAVFORMAT_LIBS} + ${FFMPEG_LIBAVDEVICE_LIBS} + ${FFMPEG_LIBAVCODEC_LIBS} + ${FFMPEG_LIBAVUTIL_LIBS} + ${FFMPEG_LIBSWSCALE_LIBS} + ) + +ELSE () + + MESSAGE(STATUS "Could not find FFMPEG") + +ENDIF() + +message(STATUS ${FFMPEG_LIBRARIES} ${FFMPEG_LIBAVFORMAT_LIBRARIES}) + +include(FindPackageHandleStandardArgs) +# handle the QUIETLY and REQUIRED arguments and set FFMPEG_FOUND to TRUE +# if all listed variables are TRUE +find_package_handle_standard_args(FFMPEG DEFAULT_MSG + FFMPEG_LIBRARIES FFMPEG_INCLUDE_DIRS) + +mark_as_advanced(FFMPEG_INCLUDE_DIRS FFMPEG_LIBRARY_DIRS FFMPEG_LIBRARIES) \ No newline at end of file diff --git a/sap.cpp b/sap.cpp new file mode 100644 index 0000000..4e2f66c --- /dev/null +++ b/sap.cpp @@ -0,0 +1,473 @@ +extern "C" { +#include +#include +#include +#include +} + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +Container split(std::string input, std::string delimiter) +{ + Container result; + + while (!input.empty()) + { + int divider = input.find(delimiter); + if (divider == std::string::npos) + { + result.push_back(input); + + input = ""; + } else { + result.push_back(input.substr(0, divider)); + + input = input.substr(divider+delimiter.length()); + } + } + + return result; +} + +template +std::string implode(InputIterator first, InputIterator last, std::string delimiter) +{ + std::stringstream result; + + for (InputIterator it = first; it != last; it++) + { + if (it != first) + { + result << delimiter; + } + + result << *it; + } + + return result.str(); +} + +int maxWordsInLine(std::vector words, Magick::Image& textimage) +{ + int result = 0; + + std::string curline = ""; + Magick::TypeMetric metric; + for (auto word : words) + { + curline += " " + word; + + textimage.fontTypeMetrics(curline, &metric); + if (metric.textWidth() > ((textimage.columns()/10)*9)) + { + break; + } else { + result++; + } + } + + return result; +} + +int minHeightRequired(std::vector words, Magick::Image& textimage) +{ + int result = 0; + while (!words.empty()) + { + int prefixlen = maxWordsInLine(words, textimage); + std::string prefixText = implode(std::begin(words), std::begin(words) + prefixlen, " "); + std::vector suffix(std::begin(words) + prefixlen, std::end(words)); + Magick::TypeMetric metric; + textimage.fontTypeMetrics(prefixText, &metric); + result += metric.textHeight() + 5; + + words = suffix; + } + + return result - 5; +} + +void layoutText(Magick::Image& textimage, Magick::Image& shadowimage, int width, int height, std::string text) +{ + DIR* fontdir; + struct dirent* ent; + if ((fontdir = opendir("fonts")) == nullptr) + { + std::cout << "Couldn't find fonts." << std::endl; + return; + } + + std::vector fonts; + while ((ent = readdir(fontdir)) != nullptr) + { + std::string dname(ent->d_name); + if ((dname.find(".otf") != std::string::npos) || (dname.find(".ttf") != std::string::npos)) + { + fonts.push_back(dname); + } + } + + closedir(fontdir); + + textimage.fillColor(Magick::Color(MaxRGB, MaxRGB, MaxRGB, MaxRGB * 0.0)); + shadowimage.fillColor(Magick::Color(0, 0, 0, 0)); + shadowimage.strokeColor("black"); + + int minSize = 48; + int realMaxSize = 96; + int maxSize = realMaxSize; + Magick::TypeMetric metric; + std::string font; + auto words = split>(text, " "); + int top = 5; + int minWords = 1; + while (!words.empty()) + { + if (font.empty() || (rand() % 10 == 0)) + { + font = fonts[rand() % fonts.size()]; + textimage.font("fonts/" + font); + shadowimage.font("fonts/" + font); + } + + int size = rand() % (maxSize - minSize + 1) + minSize; + textimage.fontPointsize(size); + int maxWords = maxWordsInLine(words, textimage); + int touse; + if (minWords > maxWords) + { + touse = maxWords; + } else { + touse = rand() % (maxWords - minWords + 1) + minWords; + } + std::string prefixText = implode(std::begin(words), std::begin(words) + touse, " "); + std::vector suffix(std::begin(words) + touse, std::end(words)); + textimage.fontTypeMetrics(prefixText, &metric); + + textimage.fontPointsize(minSize); + int lowpadding = minHeightRequired(suffix, textimage); + int freespace = height - 5 - top - lowpadding - metric.textHeight(); + std::cout << "top of " << top << " with lowpad of " << lowpadding << " and textheight of " << metric.textHeight() << " with freespace=" << freespace << std::endl; + if (freespace < 0) + { + minWords = touse; + + continue; + } + + maxSize = realMaxSize; + minWords = 1; + + int toppadding; + if (rand() % 2 == 0) + { + // Exponential distribution, biased toward top + toppadding = log(rand() % (int)exp(freespace + 1) + 1); + } else { + // Linear distribution, biased toward bottom + toppadding = rand() % (freespace + 1); + } + + int leftx = rand() % (width - 10 - (int)metric.textWidth()) + 5; + std::cout << "printing at " << leftx << "," << (top + toppadding + metric.ascent()) << std::endl; + textimage.fontPointsize(size); + textimage.annotate(prefixText, Magick::Geometry(0, 0, leftx, top + toppadding + metric.ascent())); + + shadowimage.fontPointsize(size); + shadowimage.strokeWidth(size / 10); + shadowimage.annotate(prefixText, Magick::Geometry(0, 0, leftx, top + toppadding + metric.ascent())); + //shadowimage.draw(Magick::DrawableRectangle(leftx - 5, top + toppadding, leftx + metric.textWidth() + 5, top + toppadding + metric.textHeight() + 10 + metric.descent())); + + words = suffix; + top += toppadding + metric.textHeight(); + } + + Magick::PixelPacket* shadowpixels = shadowimage.getPixels(0, 0, width, height); + Magick::PixelPacket* textpixels = textimage.getPixels(0, 0, width, height); + for (int j=0; jstreams[stream_index]; + + // find decoder for the stream + dec_ctx = st->codec; + dec = avcodec_find_decoder(dec_ctx->codec_id); + if (!dec) + { + fprintf(stderr, "Failed to find %s codec\n", av_get_media_type_string(type)); + return AVERROR(EINVAL); + } + + // Init the decoders, with or without reference counting + av_dict_set(&opts, "refcounted_frames", "0", 0); + if ((ret = avcodec_open2(dec_ctx, dec, &opts)) < 0) + { + fprintf(stderr, "Failed to open %s codec\n", av_get_media_type_string(type)); + return ret; + } + + *stream_idx = stream_index; + } + + return 0; +} + +int main(int argc, char** argv) +{ + srand(time(NULL)); + rand(); rand(); rand(); rand(); + + Magick::InitializeMagick(nullptr); + av_register_all(); + + YAML::Node config = YAML::LoadFile("config.yml"); + + twitter::auth auth; + auth.setConsumerKey(config["consumer_key"].as()); + auth.setConsumerSecret(config["consumer_secret"].as()); + auth.setAccessKey(config["access_key"].as()); + auth.setAccessSecret(config["access_secret"].as()); + + twitter::client client(auth); + + std::ifstream infile("corpus1.txt"); + std::string corpus; + std::string line; + while (getline(infile, line)) + { + if (line.back() == '\r') + { + line.pop_back(); + } + + corpus += line + " "; + } + + infile.close(); + + std::ifstream infile2("corpus2.txt"); + std::string corpus2; + while (getline(infile2, line)) + { + if (line.back() == '\r') + { + line.pop_back(); + } + + corpus2 += line + " "; + } + + infile2.close(); + + rawr kgramstats; + kgramstats.addCorpus(corpus); + kgramstats.addCorpus(corpus2); + kgramstats.compile(5); + kgramstats.setMinCorpora(2); + + DIR* videodir; + struct dirent* ent; + if ((videodir = opendir("videos")) == nullptr) + { + std::cout << "Couldn't find videos." << std::endl; + return -1; + } + + std::vector videos; + while ((ent = readdir(videodir)) != nullptr) + { + std::string dname(ent->d_name); + if (dname.find(".mp4") != std::string::npos) + { + videos.push_back(dname); + } + } + + closedir(videodir); + + for (;;) + { + std::string video = "videos/" + videos[rand() % videos.size()]; + std::cout << "Opening " << video << std::endl; + + AVFormatContext* format = nullptr; + if (avformat_open_input(&format, video.c_str(), nullptr, nullptr)) + { + std::cout << "could not open file" << std::endl; + return 1; + } + + if (avformat_find_stream_info(format, nullptr)) + { + std::cout << "could not read stream" << std::endl; + return 5; + } + + int video_stream_idx = -1; + if (open_codec_context(&video_stream_idx, format, AVMEDIA_TYPE_VIDEO)) + { + std::cout << "could not open codec" << std::endl; + return 6; + } + + AVStream* stream = format->streams[video_stream_idx]; + AVCodecContext* codec = stream->codec; + int codecw = codec->width; + int codech = codec->height; + + int64_t seek = (rand() % format->duration) * codec->time_base.num / codec->time_base.den; + std::cout << seek << std::endl; + if (av_seek_frame(format, video_stream_idx, seek, 0)) + { + std::cout << "could not seek" << std::endl; + return 4; + } + + AVPacket packet; + av_init_packet(&packet); + + AVFrame* frame = av_frame_alloc(); + AVFrame* converted = av_frame_alloc(); + + int buffer_size = av_image_get_buffer_size(AV_PIX_FMT_RGB24, codecw, codech, 1); + uint8_t* buffer = new uint8_t[buffer_size]; + + av_image_alloc(converted->data, converted->linesize, codecw, codech, AV_PIX_FMT_RGB24, 1); + + for (;;) + { + if (av_read_frame(format, &packet)) + { + std::cout << "could not read frame" << std::endl; + return 2; + } + + if (packet.stream_index != video_stream_idx) + { + continue; + } + + int got_pic; + if (avcodec_decode_video2(codec, frame, &got_pic, &packet) < 0) + { + std::cout << "could not decode frame" << std::endl; + return 7; + } + + if (!got_pic) + { + continue; + } + + if (packet.flags && AV_PKT_FLAG_KEY) + { + SwsContext* sws = sws_getContext(codecw, codech, codec->pix_fmt, codecw, codech, AV_PIX_FMT_RGB24, 0, nullptr, nullptr, 0); + sws_scale(sws, frame->data, frame->linesize, 0, codech, converted->data, converted->linesize); + sws_freeContext(sws); + + av_image_copy_to_buffer(buffer, buffer_size, converted->data, converted->linesize, AV_PIX_FMT_RGB24, codecw, codech, 1); + av_frame_free(&frame); + av_frame_free(&converted); + av_packet_unref(&packet); + avcodec_close(codec); + avformat_close_input(&format); + + int width = 1024; + int height = codech * width / codecw; + + Magick::Image image; + image.read(codecw, codech, "RGB", Magick::CharPixel, buffer); + image.zoom(Magick::Geometry(width, height)); + + std::string action = kgramstats.randomSentence(rand() % 15 + 5); + Magick::Image textimage(Magick::Geometry(width, height), "transparent"); + Magick::Image shadowimage(Magick::Geometry(width, height), "transparent"); + layoutText(textimage, shadowimage, width, height, action); + image.composite(shadowimage, 0, 0, Magick::OverCompositeOp); + image.composite(textimage, 0, 0, Magick::OverCompositeOp); + + image.magick("jpeg"); + + Magick::Blob outputimg; + image.write(&outputimg); + + delete[] buffer; + + std::cout << "Generated image." << std::endl << "Tweeting..." << std::endl; + + long media_id; + twitter::response resp = client.uploadMedia("image/jpeg", (const char*) outputimg.data(), outputimg.length(), media_id); + if (resp != twitter::response::ok) + { + std::cout << "Twitter error while uploading image: " << resp << std::endl; + + break; + } + + twitter::tweet tw; + resp = client.updateStatus("", tw, twitter::tweet(), {media_id}); + if (resp != twitter::response::ok) + { + std::cout << "Twitter error while tweeting: " << resp << std::endl; + + break; + } + + std::cout << "Done!" << std::endl << "Waiting..." << std::endl << std::endl; + + break; + } + } + + std::this_thread::sleep_for(std::chrono::hours(1)); + } + + return 0; +} diff --git a/vendor/rawr-ebooks b/vendor/rawr-ebooks new file mode 160000 index 0000000..5ce05b8 --- /dev/null +++ b/vendor/rawr-ebooks @@ -0,0 +1 @@ +Subproject commit 5ce05b81520d06a78165c5c5039007c9f29d4b23 -- cgit 1.4.1