about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKelly Rauchenberger <fefferburbia@gmail.com>2017-04-10 11:35:55 -0400
committerKelly Rauchenberger <fefferburbia@gmail.com>2017-04-10 11:35:55 -0400
commitbd8ff4083d41417f014241ddb94e43b39bee6080 (patch)
tree09ec764e995b53de78462b2fb996db5973d25aef
parent95e89d24d998c75dae191ceefbe17c0d44c2d78a (diff)
downloaddifference-bd8ff4083d41417f014241ddb94e43b39bee6080.tar.gz
difference-bd8ff4083d41417f014241ddb94e43b39bee6080.tar.bz2
difference-bd8ff4083d41417f014241ddb94e43b39bee6080.zip
Updated verbly (new API)
Also updated libtwitter++, and blacklisted some problematic images.
-rw-r--r--CMakeLists.txt9
-rw-r--r--difference.cpp663
-rw-r--r--difference.h58
-rw-r--r--main.cpp35
m---------vendor/libtwittercpp0
m---------vendor/verbly0
6 files changed, 475 insertions, 290 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index c7ae7a9..adb9ba1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -1,18 +1,15 @@
1cmake_minimum_required (VERSION 3.1) 1cmake_minimum_required (VERSION 3.1)
2project (difference) 2project (difference)
3 3
4set(CMAKE_BUILD_TYPE Debug)
5
6add_subdirectory(vendor/libtwittercpp) 4add_subdirectory(vendor/libtwittercpp)
7add_subdirectory(vendor/verbly) 5add_subdirectory(vendor/verbly)
8add_subdirectory(vendor/yaml-cpp EXCLUDE_FROM_ALL) 6add_subdirectory(vendor/yaml-cpp EXCLUDE_FROM_ALL)
9 7
10find_package(PkgConfig) 8find_package(PkgConfig)
11pkg_check_modules(sqlite3 sqlite3 REQUIRED)
12pkg_check_modules(GraphicsMagick GraphicsMagick++ REQUIRED) 9pkg_check_modules(GraphicsMagick GraphicsMagick++ REQUIRED)
13 10
14include_directories(vendor/libtwittercpp/src vendor/libtwittercpp/vendor/curlcpp/include ${sqlite3_INCLUDE_DIR} vendor/verbly/lib ${GraphicsMagick_INCLUDE_DIRS} vendor/yaml-cpp/include) 11include_directories(vendor/libtwittercpp/src vendor/libtwittercpp/vendor/curlcpp/include vendor/verbly/lib ${GraphicsMagick_INCLUDE_DIRS} vendor/yaml-cpp/include)
15add_executable(difference difference.cpp) 12add_executable(difference difference.cpp main.cpp)
16set_property(TARGET difference PROPERTY CXX_STANDARD 11) 13set_property(TARGET difference PROPERTY CXX_STANDARD 11)
17set_property(TARGET difference PROPERTY CXX_STANDARD_REQUIRED ON) 14set_property(TARGET difference PROPERTY CXX_STANDARD_REQUIRED ON)
18target_link_libraries(difference verbly ${sqlite3_LIBRARIES} yaml-cpp twitter++ ${GraphicsMagick_LIBRARIES}) 15target_link_libraries(difference verbly yaml-cpp twitter++ ${GraphicsMagick_LIBRARIES})
diff --git a/difference.cpp b/difference.cpp index b9290cb..123a040 100644 --- a/difference.cpp +++ b/difference.cpp
@@ -1,344 +1,439 @@
1#include <verbly.h> 1#include "difference.h"
2#include <twitter.h>
3#include <curl_easy.h> 2#include <curl_easy.h>
4#include <curl_header.h> 3#include <curl_header.h>
5#include <list>
6#include <set>
7#include <vector> 4#include <vector>
8#include <algorithm>
9#include <Magick++.h>
10#include <iostream> 5#include <iostream>
11#include <yaml-cpp/yaml.h> 6#include <yaml-cpp/yaml.h>
12#include <chrono> 7#include <chrono>
13#include <thread> 8#include <thread>
14#include <random> 9#include <deque>
15 10
16std::string capitalize(std::string input) 11difference::difference(
12 std::string configFile,
13 std::mt19937& rng) :
14 rng_(rng)
17{ 15{
18 std::string result; 16 // Load the config file.
19 bool capnext = true; 17 YAML::Node config = YAML::LoadFile(configFile);
20 18
21 for (auto ch : input) 19 // Set up the Twitter client.
20 twitter::auth auth;
21 auth.setConsumerKey(config["consumer_key"].as<std::string>());
22 auth.setConsumerSecret(config["consumer_secret"].as<std::string>());
23 auth.setAccessKey(config["access_key"].as<std::string>());
24 auth.setAccessSecret(config["access_secret"].as<std::string>());
25
26 client_ = std::unique_ptr<twitter::client>(new twitter::client(auth));
27
28 // Set up the verbly database.
29 database_ = std::unique_ptr<verbly::database>(
30 new verbly::database(config["verbly_datafile"].as<std::string>()));
31
32 fontfile_ = "@" + config["font"].as<std::string>();
33}
34
35void difference::run() const
36{
37 for (;;)
22 { 38 {
23 if (capnext) 39 std::cout << "Generating tweet..." << std::endl;
40
41 try
24 { 42 {
25 result += toupper(ch); 43 // Find a noun to use as the pictured item.
26 capnext = false; 44 std::cout << "Choosing pictured noun..." << std::endl;
27 } else { 45
28 result += ch; 46 verbly::word pictured = getPicturedNoun();
47
48 std::cout << "Noun: " << pictured.getBaseForm().getText() << std::endl;
49
50 // Choose two pictures of that noun.
51 std::cout << "Finding an image..." << std::endl;
52
53 Magick::Image image1;
54 Magick::Image image2;
55 std::tie(image1, image2) = getImagesForNoun(pictured);
56
57 // Choose two opposite words.
58 std::cout << "Choosing two opposite words..." << std::endl;
59
60 verbly::word word1;
61 verbly::word word2;
62 std::tie(word1, word2) = getOppositeIdentifiers();
63
64 // Compose the images and words.
65 std::cout << "Composing image..." << std::endl;
66
67 Magick::Image image = composeImage(
68 std::move(image1),
69 word1,
70 std::move(image2),
71 word2);
72
73 // Generate the tweet text.
74 std::cout << "Generating text..." << std::endl;
75
76 std::string text = generateTweetText(std::move(word1), std::move(word2));
77
78 std::cout << "Tweet text: " << text << std::endl;
79
80 // Send the tweet.
81 std::cout << "Sending tweet..." << std::endl;
82
83 sendTweet(std::move(text), std::move(image));
84
85 std::cout << "Tweeted!" << std::endl;
86
87 // Wait.
88 std::this_thread::sleep_for(std::chrono::hours(1));
89 } catch (const could_not_get_images& ex)
90 {
91 std::cout << ex.what() << std::endl;
92 } catch (const Magick::ErrorImage& ex)
93 {
94 std::cout << "Image error: " << ex.what() << std::endl;
95 } catch (const Magick::ErrorCorruptImage& ex)
96 {
97 std::cout << "Corrupt image: " << ex.what() << std::endl;
98 } catch (const twitter::twitter_error& ex)
99 {
100 std::cout << "Twitter error: " << ex.what() << std::endl;
101
102 std::this_thread::sleep_for(std::chrono::hours(1));
29 } 103 }
30 104
31 if ((ch == ' ') || (ch == '-')) 105 std::cout << std::endl;
106 }
107}
108
109verbly::word difference::getPicturedNoun() const
110{
111 verbly::filter whitelist =
112 (verbly::notion::wnid == 109287968) // Geological formations
113 || (verbly::notion::wnid == 109208496) // Asterisms (collections of stars)
114 || (verbly::notion::wnid == 109239740) // Celestial bodies
115 || (verbly::notion::wnid == 109277686) // Exterrestrial objects (comets and meteroids)
116 || (verbly::notion::wnid == 109403211) // Radiators (supposedly natural radiators but actually these are just pictures of radiators)
117 || (verbly::notion::wnid == 109416076) // Rocks
118 || (verbly::notion::wnid == 105442131) // Chromosomes
119 || (verbly::notion::wnid == 100324978) // Tightrope walking
120 || (verbly::notion::wnid == 100326094) // Rock climbing
121 || (verbly::notion::wnid == 100433458) // Contact sports
122 || (verbly::notion::wnid == 100433802) // Gymnastics
123 || (verbly::notion::wnid == 100439826) // Track and field
124 || (verbly::notion::wnid == 100440747) // Skiing
125 || (verbly::notion::wnid == 100441824) // Water sport
126 || (verbly::notion::wnid == 100445351) // Rowing
127 || (verbly::notion::wnid == 100446980) // Archery
128 // TODO: add more sports
129 || (verbly::notion::wnid == 100021939) // Artifacts
130 || (verbly::notion::wnid == 101471682) // Vertebrates
131 ;
132
133 verbly::filter blacklist =
134 (verbly::notion::wnid == 106883725) // swastika
135 || (verbly::notion::wnid == 104416901) // tetraskele
136 || (verbly::notion::wnid == 102512053) // fish
137 || (verbly::notion::wnid == 103575691) // instrument of execution
138 || (verbly::notion::wnid == 103829563) // noose
139 ;
140
141 verbly::query<verbly::word> pictureQuery = database_->words(
142 (verbly::notion::fullHypernyms %= whitelist)
143 && !(verbly::notion::fullHypernyms %= blacklist)
144 && (verbly::notion::partOfSpeech == verbly::part_of_speech::noun)
145 && (verbly::notion::numOfImages >= 2));
146
147 verbly::word pictured = pictureQuery.first();
148
149 return pictured;
150}
151
152std::pair<Magick::Image, Magick::Image>
153 difference::getImagesForNoun(verbly::word pictured) const
154{
155 int backoff = 0;
156
157 std::cout << "Getting URLs..." << std::endl;
158
159 std::string lstdata;
160 while (lstdata.empty())
161 {
162 std::ostringstream lstbuf;
163 curl::curl_ios<std::ostringstream> lstios(lstbuf);
164 curl::curl_easy lsthandle(lstios);
165 std::string lsturl = pictured.getNotion().getImageNetUrl();
166 lsthandle.add<CURLOPT_URL>(lsturl.c_str());
167
168 try
169 {
170 lsthandle.perform();
171 } catch (const curl::curl_easy_exception& e)
172 {
173 e.print_traceback();
174
175 backoff++;
176 std::cout << "Waiting for " << backoff << " seconds..." << std::endl;
177
178 std::this_thread::sleep_for(std::chrono::seconds(backoff));
179
180 continue;
181 }
182
183 backoff = 0;
184
185 if (lsthandle.get_info<CURLINFO_RESPONSE_CODE>().get() != 200)
32 { 186 {
33 capnext = true; 187 throw could_not_get_images();
34 } 188 }
189
190 std::cout << "Got URLs." << std::endl;
191 lstdata = lstbuf.str();
192 }
193
194 std::vector<std::string> lstvec = verbly::split<std::vector<std::string>>(lstdata, "\r\n");
195 if (lstvec.size() < 2)
196 {
197 throw could_not_get_images();
35 } 198 }
36 199
37 return result; 200 std::shuffle(std::begin(lstvec), std::end(lstvec), rng_);
201
202 std::deque<std::string> urls;
203 for (std::string& url : lstvec)
204 {
205 urls.push_back(url);
206 }
207
208 Magick::Image image1;
209 bool success = false;
210 while (!urls.empty())
211 {
212 std::string url = urls.front();
213 urls.pop_front();
214
215 try
216 {
217 image1 = getImageAtUrl(url);
218
219 success = true;
220 break;
221 } catch (const could_not_get_images& ex)
222 {
223 // Just try the next one.
224 }
225 }
226
227 if (!success)
228 {
229 throw could_not_get_images();
230 }
231
232 Magick::Image image2;
233 success = false;
234 while (!urls.empty())
235 {
236 std::string url = urls.front();
237 urls.pop_front();
238
239 try
240 {
241 image2 = getImageAtUrl(url);
242
243 success = true;
244 break;
245 } catch (const could_not_get_images& ex)
246 {
247 // Just try the next one.
248 }
249 }
250
251 if (!success)
252 {
253 throw could_not_get_images();
254 }
255
256 return {std::move(image1), std::move(image2)};
38} 257}
39 258
40bool downloadImage(std::string url, curl::curl_header headers, Magick::Blob& img, Magick::Image& pic) 259Magick::Image difference::getImageAtUrl(std::string url) const
41{ 260{
42 // willyfogg.com is a thumbnail generator known to return 200 even if the target image no longer exists 261 // willyfogg.com is a thumbnail generator known to return 200 even if the target image no longer exists
43 if (url.find("willyfogg.com/thumb.php") != std::string::npos) 262 if (url.find("willyfogg.com/thumb.php") != std::string::npos)
44 { 263 {
45 return false; 264 throw could_not_get_images();
46 } 265 }
47 266
267 // Accept string from Google Chrome
268 std::string accept = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8";
269 curl::curl_header headers;
270 headers.add(accept);
271
48 std::ostringstream imgbuf; 272 std::ostringstream imgbuf;
49 curl::curl_ios<std::ostringstream> imgios(imgbuf); 273 curl::curl_ios<std::ostringstream> imgios(imgbuf);
50 curl::curl_easy imghandle(imgios); 274 curl::curl_easy imghandle(imgios);
51 275
52 imghandle.add<CURLOPT_HTTPHEADER>(headers.get()); 276 imghandle.add<CURLOPT_HTTPHEADER>(headers.get());
53 imghandle.add<CURLOPT_URL>(url.c_str()); 277 imghandle.add<CURLOPT_URL>(url.c_str());
54 imghandle.add<CURLOPT_CONNECTTIMEOUT>(30); 278 imghandle.add<CURLOPT_CONNECTTIMEOUT>(30);
55 279
56 try { 280 try
281 {
57 imghandle.perform(); 282 imghandle.perform();
58 } catch (curl::curl_easy_exception error) { 283 } catch (curl::curl_easy_exception error) {
59 error.print_traceback(); 284 error.print_traceback();
60 285
61 return false; 286 throw could_not_get_images();
62 } 287 }
63 288
64 if (imghandle.get_info<CURLINFO_RESPONSE_CODE>().get() != 200) 289 if (imghandle.get_info<CURLINFO_RESPONSE_CODE>().get() != 200)
65 { 290 {
66 return false; 291 throw could_not_get_images();
67 } 292 }
68 293
69 std::string content_type = imghandle.get_info<CURLINFO_CONTENT_TYPE>().get(); 294 std::string content_type = imghandle.get_info<CURLINFO_CONTENT_TYPE>().get();
70 if (content_type.substr(0, 6) != "image/") 295 if (content_type.substr(0, 6) != "image/")
71 { 296 {
72 return false; 297 throw could_not_get_images();
73 } 298 }
74 299
75 std::string imgstr = imgbuf.str(); 300 std::string imgstr = imgbuf.str();
76 img = Magick::Blob(imgstr.c_str(), imgstr.length()); 301 Magick::Blob img(imgstr.c_str(), imgstr.length());
77 pic.read(img); 302 Magick::Image pic;
78 if (pic.rows() == 0) 303
304 try
305 {
306 pic.read(img);
307
308 if ((pic.rows() > 0) && (pic.columns() >= 800))
309 {
310 std::cout << url << std::endl;
311 }
312 } catch (const Magick::ErrorOption& e)
79 { 313 {
80 return false; 314 // Occurs when the the data downloaded from the server is malformed
315 std::cout << "Magick: " << e.what() << std::endl;
316
317 throw could_not_get_images();
81 } 318 }
82 319
83 std::cout << url << std::endl; 320 return pic;
84
85 return true;
86} 321}
87 322
88int main(int argc, char** argv) 323std::pair<verbly::word, verbly::word> difference::getOppositeIdentifiers() const
89{ 324{
90 Magick::InitializeMagick(nullptr); 325 verbly::part_of_speech pos;
91 326
92 if (argc != 2) 327 if (std::bernoulli_distribution(1.0/2.0)(rng_))
93 { 328 {
94 std::cout << "usage: difference [configfile]" << std::endl; 329 pos = verbly::part_of_speech::noun;
95 return -1; 330 } else {
331 pos = verbly::part_of_speech::adjective;
96 } 332 }
97 333
98 std::string configfile(argv[1]); 334 verbly::word w1 = database_->words(
99 YAML::Node config = YAML::LoadFile(configfile); 335 (verbly::notion::partOfSpeech == pos)
336 && (verbly::word::antonyms)).first();
100 337
101 int delay = 60 * 60; 338 verbly::word w2 = database_->words(
102 339 (verbly::notion::partOfSpeech == pos)
103 twitter::auth auth; 340 && (verbly::word::antonyms %= w1)).first();
104 auth.setConsumerKey(config["consumer_key"].as<std::string>()); 341
105 auth.setConsumerSecret(config["consumer_secret"].as<std::string>()); 342 return {std::move(w1), std::move(w2)};
106 auth.setAccessKey(config["access_key"].as<std::string>()); 343}
107 auth.setAccessSecret(config["access_secret"].as<std::string>()); 344
108 345Magick::Image difference::composeImage(
109 twitter::client client(auth); 346 Magick::Image image1,
110 347 verbly::word word1,
111 std::random_device random_device; 348 Magick::Image image2,
112 std::mt19937 random_engine{random_device()}; 349 verbly::word word2) const
113 350{
114 std::string fontfile = "@" + config["font"].as<std::string>(); 351 if (image1.columns() < 320)
115
116 verbly::data database(config["verbly_datafile"].as<std::string>());
117
118 auto whitelist = database.nouns();
119 whitelist.with_wnid(111530512); // Crops (plants)
120 whitelist.with_wnid(111536087); // Ornamental (plants)
121 whitelist.with_wnid(111536230); // Pot plants
122 whitelist.with_wnid(113083023); // Houseplants
123 whitelist.with_wnid(113083306); // Garden plants
124 whitelist.with_wnid(113083586); // Vascular plants (includes flowers)
125 whitelist.with_wnid(109287968); // Geological formations
126 whitelist.with_wnid(109208496); // Asterisms (collections of stars)
127 whitelist.with_wnid(109239740); // Celestial bodies
128 whitelist.with_wnid(109277686); // Exterrestrial objects (comets and meteroids)
129 whitelist.with_wnid(109403211); // Radiators (supposedly natural radiators but actually these are just pictures of radiators)
130 whitelist.with_wnid(109416076); // Rocks
131 whitelist.with_wnid(105442131); // Chromosomes
132 whitelist.with_wnid(100324978); // Tightrope walking
133 whitelist.with_wnid(100326094); // Rock climbing
134 whitelist.with_wnid(100433458); // Contact sports
135 whitelist.with_wnid(100433802); // Gymnastics
136 whitelist.with_wnid(100439826); // Track and field
137 whitelist.with_wnid(100440747); // Skiing
138 whitelist.with_wnid(100441824); // Water sport
139 whitelist.with_wnid(100445351); // Rowing
140 whitelist.with_wnid(100446980); // Archery
141 // TODO: add more sports
142 whitelist.with_wnid(100021939); // Artifacts
143 whitelist.with_wnid(101471682); // Vertebrates
144
145 auto whitedata = whitelist.run();
146 std::set<int> wnids;
147 verbly::filter<verbly::noun> whitefilter;
148 whitefilter.set_orlogic(true);
149 for (auto wd : whitedata)
150 { 352 {
151 if (wnids.count(wd.wnid()) == 0) 353 image1.zoom(Magick::Geometry(320, image1.rows() * 320 / image1.columns(), 0, 0));
152 {
153 whitefilter << wd;
154 wnids.insert(wd.wnid());
155 }
156 } 354 }
157 355
158 // Accept string from Google Chrome 356 if (image2.columns() < 320)
159 std::string accept = "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"; 357 {
160 curl::curl_header headers; 358 image2.zoom(Magick::Geometry(320, image2.rows() * 320 / image2.columns(), 0, 0));
161 headers.add(accept); 359 }
162 360
163 std::cout << "Started!" << std::endl; 361 int width = std::min(image1.columns(), image2.columns());
362 int height = std::min(image1.rows(), image2.rows());
363 Magick::Geometry geo1(width, height, image1.columns()/2 - width/2, image1.rows()/2 - height/2);
364 Magick::Geometry geo2(width, height, image2.columns()/2 - width/2, image2.rows()/2 - height/2);
365 image1.crop(geo1);
366 image2.crop(geo2);
367
368 Magick::Image composite(Magick::Geometry(width*2, height, 0, 0), "white");
369 composite.draw(Magick::DrawableCompositeImage(0, 0, image1));
370 composite.draw(Magick::DrawableCompositeImage(width, 0, image2));
371 composite.font(fontfile_);
372
373 std::string ant1 = word1.getBaseForm().getText();
374 std::string ant2 = word2.getBaseForm().getText();
375
376 double fontsize = 72;
164 for (;;) 377 for (;;)
165 { 378 {
166 try 379 composite.fontPointsize(fontsize);
167 { 380
168 std::cout << "Generating noun..." << std::endl; 381 Magick::TypeMetric metric;
169 verbly::noun pictured = database.nouns().full_hyponym_of(whitefilter).at_least_n_images(2).random().limit(1).run().front(); 382 composite.fontTypeMetrics(ant1, &metric);
170 std::cout << "Noun: " << pictured.singular_form() << std::endl; 383 if (metric.textWidth() > 300)
171 std::cout << "Getting URLs..." << std::endl;
172 std::ostringstream lstbuf;
173 curl::curl_ios<std::ostringstream> lstios(lstbuf);
174 curl::curl_easy lsthandle(lstios);
175 std::string lsturl = pictured.imagenet_url();
176 lsthandle.add<CURLOPT_URL>(lsturl.c_str());
177 lsthandle.perform();
178
179 if (lsthandle.get_info<CURLINFO_RESPONSE_CODE>().get() != 200)
180 {
181 std::cout << "Could not get URLs" << std::endl;
182 continue;
183 }
184
185 std::cout << "Got URLs." << std::endl;
186 std::string lstdata = lstbuf.str();
187 auto lstlist = verbly::split<std::list<std::string>>(lstdata, "\r\n");
188 std::set<std::string> lstset;
189 for (auto url : lstlist)
190 {
191 lstset.insert(url);
192 }
193
194 if (lstset.size() < 2)
195 {
196 continue;
197 }
198
199 std::vector<std::string> lstvec;
200 for (auto url : lstset)
201 {
202 lstvec.push_back(url);
203 }
204
205 std::shuffle(std::begin(lstvec), std::end(lstvec), random_engine);
206
207 Magick::Blob img1;
208 Magick::Image pic1;
209 bool success = false;
210 int curind = 0;
211 for (; curind < lstvec.size(); curind++)
212 {
213 if (downloadImage(lstvec[curind], headers, img1, pic1))
214 {
215 success = true;
216 break;
217 }
218 }
219
220 if (!success)
221 {
222 continue;
223 }
224
225 success = false;
226 Magick::Blob img2;
227 Magick::Image pic2;
228 for (curind++; curind < lstvec.size(); curind++)
229 {
230 if (downloadImage(lstvec[curind], headers, img2, pic2))
231 {
232 success = true;
233 break;
234 }
235 }
236
237 if (!success)
238 {
239 continue;
240 }
241
242 std::string ant1, ant2;
243 std::uniform_int_distribution<int> coinflip(0, 1);
244 if (coinflip(random_engine)==0)
245 {
246 verbly::noun n1 = database.nouns().has_antonyms().random().limit(1).run().front();
247 verbly::noun n2 = n1.antonyms().random().limit(1).run().front();
248 ant1 = n1.singular_form();
249 ant2 = n2.singular_form();
250 } else {
251 verbly::adjective a1 = database.adjectives().has_antonyms().random().limit(1).run().front();
252 verbly::adjective a2 = a1.antonyms().random().limit(1).run().front();
253 ant1 = a1.base_form();
254 ant2 = a2.base_form();
255 }
256
257 if (pic1.columns() < 320)
258 {
259 pic1.zoom(Magick::Geometry(320, pic1.rows() * 320 / pic1.columns(), 0, 0));
260 }
261
262 if (pic2.columns() < 320)
263 {
264 pic2.zoom(Magick::Geometry(320, pic2.rows() * 320 / pic2.columns(), 0, 0));
265 }
266
267 int width = std::min(pic1.columns(), pic2.columns());
268 int height = std::min(pic1.rows(), pic2.rows());
269 Magick::Geometry geo1(width, height, pic1.columns()/2 - width/2, pic1.rows()/2 - height/2);
270 Magick::Geometry geo2(width, height, pic2.columns()/2 - width/2, pic2.rows()/2 - height/2);
271 pic1.crop(geo1);
272 pic2.crop(geo2);
273
274 Magick::Image composite(Magick::Geometry(width*2, height, 0, 0), "white");
275 composite.draw(Magick::DrawableCompositeImage(0, 0, pic1));
276 composite.draw(Magick::DrawableCompositeImage(width, 0, pic2));
277 composite.font(fontfile);
278
279 double fontsize = 72;
280 for (;;)
281 {
282 composite.fontPointsize(fontsize);
283
284 Magick::TypeMetric metric;
285 composite.fontTypeMetrics(ant1, &metric);
286 if (metric.textWidth() > 300)
287 {
288 fontsize -= 0.5;
289 continue;
290 }
291
292 composite.fontTypeMetrics(ant2, &metric);
293 if (metric.textWidth() > 300)
294 {
295 fontsize -= 0.5;
296 continue;
297 }
298
299 break;
300 }
301
302 Magick::TypeMetric metric;
303 composite.fontTypeMetrics(ant1, &metric);
304 std::uniform_int_distribution<int> rowdist(20, (int)(composite.rows() - 19 - metric.textHeight()));
305 int y = rowdist(random_engine);
306 y = composite.rows() - y;
307 int x1 = (width - 40 - metric.textWidth())/2+20;
308 composite.fontTypeMetrics(ant2, &metric);
309 int x2 = (width - 40 - metric.textWidth())/2+20+width;
310 composite.strokeColor("white");
311 composite.strokeWidth(2);
312 composite.antiAlias(false);
313 composite.draw(Magick::DrawableText(x1, y, ant1));
314 composite.draw(Magick::DrawableText(x2, y, ant2));
315
316 composite.magick("png");
317
318 Magick::Blob outputimg;
319 composite.write(&outputimg);
320
321 std::cout << "Generated image!" << std::endl << "Tweeting..." << std::endl;
322
323 std::stringstream msg;
324 msg << capitalize(ant1);
325 msg << " vs. ";
326 msg << capitalize(ant2);
327
328 long media_id = client.uploadMedia("image/png", (const char*) outputimg.data(), outputimg.length());
329 client.updateStatus(msg.str(), {media_id});
330
331 std::cout << "Done!" << std::endl << "Waiting..." << std::endl << std::endl;
332 } catch (const curl::curl_easy_exception& error)
333 { 384 {
334 error.print_traceback(); 385 fontsize -= 0.5;
335 } catch (const twitter::twitter_error& e) 386 continue;
387 }
388
389 composite.fontTypeMetrics(ant2, &metric);
390 if (metric.textWidth() > 300)
336 { 391 {
337 std::cout << "Twitter error: " << e.what() << std::endl; 392 fontsize -= 0.5;
393 continue;
338 } 394 }
339 395
340 std::this_thread::sleep_for(std::chrono::seconds(delay)); 396 break;
341 } 397 }
342 398
343 return 0; 399 Magick::TypeMetric metric;
400 composite.fontTypeMetrics(ant1, &metric);
401 std::uniform_int_distribution<int> rowdist(20, (int)(composite.rows() - 19 - metric.textHeight()));
402 int y = rowdist(rng_);
403 y = composite.rows() - y;
404 int x1 = (width - 40 - metric.textWidth())/2+20;
405 composite.fontTypeMetrics(ant2, &metric);
406 int x2 = (width - 40 - metric.textWidth())/2+20+width;
407 composite.strokeColor("white");
408 composite.strokeWidth(2);
409 composite.antiAlias(false);
410 composite.draw(Magick::DrawableText(x1, y, ant1));
411 composite.draw(Magick::DrawableText(x2, y, ant2));
412
413 composite.magick("png");
414
415 return composite;
416}
417
418std::string difference::generateTweetText(
419 verbly::word word1,
420 verbly::word word2) const
421{
422 verbly::token result {
423 verbly::token::capitalize(verbly::token::casing::capitalize, word1),
424 "vs.",
425 verbly::token::capitalize(verbly::token::casing::capitalize, word2)
426 };
427
428 return result.compile();
429}
430
431void difference::sendTweet(std::string text, Magick::Image image) const
432{
433 Magick::Blob outputBlob;
434 image.magick("png");
435 image.write(&outputBlob);
436
437 long media_id = client_->uploadMedia("image/png", (const char*) outputBlob.data(), outputBlob.length());
438 client_->updateStatus(std::move(text), {media_id});
344} 439}
diff --git a/difference.h b/difference.h new file mode 100644 index 0000000..bccf9ab --- /dev/null +++ b/difference.h
@@ -0,0 +1,58 @@
1#ifndef DIFFERENCE_H_081276A3
2#define DIFFERENCE_H_081276A3
3
4#include <random>
5#include <string>
6#include <Magick++.h>
7#include <utility>
8#include <verbly.h>
9#include <stdexcept>
10#include <twitter.h>
11#include <memory>
12
13class difference {
14public:
15
16 difference(
17 std::string configFile,
18 std::mt19937& rng);
19
20 void run() const;
21
22private:
23
24 verbly::word getPicturedNoun() const;
25
26 std::pair<Magick::Image, Magick::Image>
27 getImagesForNoun(verbly::word pictured) const;
28
29 Magick::Image getImageAtUrl(std::string url) const;
30
31 std::pair<verbly::word, verbly::word> getOppositeIdentifiers() const;
32
33 Magick::Image composeImage(
34 Magick::Image image1,
35 verbly::word word1,
36 Magick::Image image2,
37 verbly::word word2) const;
38
39 std::string generateTweetText(verbly::word word1, verbly::word word2) const;
40
41 void sendTweet(std::string text, Magick::Image image) const;
42
43 class could_not_get_images : public std::runtime_error {
44 public:
45
46 could_not_get_images() : std::runtime_error("Could not get images for noun")
47 {
48 }
49 };
50
51 std::mt19937& rng_;
52 std::unique_ptr<verbly::database> database_;
53 std::unique_ptr<twitter::client> client_;
54 std::string fontfile_;
55
56};
57
58#endif /* end of include guard: DIFFERENCE_H_081276A3 */
diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..1b49563 --- /dev/null +++ b/main.cpp
@@ -0,0 +1,35 @@
1#include "difference.h"
2
3int main(int argc, char** argv)
4{
5 Magick::InitializeMagick(nullptr);
6
7 std::random_device randomDevice;
8 std::mt19937 rng(randomDevice());
9
10 if (argc != 2)
11 {
12 std::cout << "usage: difference [configfile]" << std::endl;
13 return -1;
14 }
15
16 std::string configfile(argv[1]);
17
18 try
19 {
20 difference bot(configfile, rng);
21
22 try
23 {
24 bot.run();
25 } catch (const std::exception& ex)
26 {
27 std::cout << "Error running bot: " << ex.what() << std::endl;
28 }
29 } catch (const std::exception& ex)
30 {
31 std::cout << "Error initializing bot: " << ex.what() << std::endl;
32 }
33
34 return 0;
35}
diff --git a/vendor/libtwittercpp b/vendor/libtwittercpp
Subproject d90a1e74c77ba67f25a812609fd49d479bc464d Subproject df906121dd862c0f704e44f28ee079158c431c4
diff --git a/vendor/verbly b/vendor/verbly
Subproject 1f898f3bd66c29672275c2c884b17ba662ced62 Subproject 59eab842de02b2b2ba8bf53e2214b558457e635