├── .gitignore ├── Data └── Gif_Data │ └── gif_data.json ├── LICENSE ├── README.md ├── prepare_data.py ├── python_prototype ├── GifFinder.py ├── __pycache__ │ └── GifFinder.cpython-36.pyc ├── demo.py ├── main.py └── requirements.txt ├── setup.sh └── web_interface ├── opengif.html └── static ├── css └── style.css ├── js └── gif_search.js └── transparent.gif /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | __pycache__/ 3 | web_interface/static/gifs/*.gif 4 | Data/WordVectors/* 5 | -------------------------------------------------------------------------------- /Data/Gif_Data/gif_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "gifs": [ 3 | { 4 | "title" : "Cat", 5 | "filename" : "cat.gif", 6 | "description" : "cat typing computer work" 7 | }, 8 | { 9 | "title" : "Kermit Dancing", 10 | "filename" : "frog.gif", 11 | "description" : "dancing frog muppet song" 12 | }, 13 | { 14 | "title" : "Chicken DJ", 15 | "filename" : "chicken.gif", 16 | "description" : "chicken music dance" 17 | }, 18 | { 19 | "title" : "Dancing Skeleton", 20 | "filename" : "Dancing_skull.gif", 21 | "description" : "skeleton dance" 22 | }, 23 | { 24 | "title" : "Zombie Rising", 25 | "filename" : "Zombie-sits-up-in-the-grave.gif", 26 | "description" : "zombie grave dead" 27 | }, 28 | { 29 | "title" : "Corpse Crawling", 30 | "filename" : "Stayin-alive.gif", 31 | "description" : "zombie corpse dead" 32 | }, 33 | { 34 | "title" : "3d Wiggle", 35 | "filename" : "WiggleVision.gif", 36 | "description" : "desert hula" 37 | }, 38 | { 39 | "title" : "Chocolate Hearts", 40 | "filename" : "Animated-valentines-chocolate.gif", 41 | "description" : "heart love" 42 | }, 43 | { 44 | "title" : "Tropical Beach", 45 | "filename" : "tropical-beach.gif", 46 | "description" : "beach" 47 | }, 48 | { 49 | "title" : "Ocean Sunset", 50 | "filename" : "ocean_sunset.gif", 51 | "description" : "ocean sunset" 52 | }, 53 | { 54 | "title" : "Human Dolphin", 55 | "filename" : "Human-dolphin.gif", 56 | "description" : "swimming" 57 | }, 58 | { 59 | "title" : "faucet", 60 | "filename" : "faucet.gif", 61 | "description" : "water drop" 62 | }, 63 | { 64 | "title" : "werewolf", 65 | "filename" : "5081_1b73_495.gif", 66 | "description" : "wolf confused" 67 | }, 68 | { 69 | "title" : "wtf dancing", 70 | "filename" : "Moving-animated-picture-of-two-dancers.gif", 71 | "description" : "strange dancing" 72 | }, 73 | { 74 | "title" : "Beard Shave", 75 | "filename" : "funny-gifs-beard-alaga.gif", 76 | "description" : "beard" 77 | }, 78 | { 79 | "title" : "UFO", 80 | "filename" : "ufo112.gif", 81 | "description" : "alien" 82 | }, 83 | { 84 | "title" : "campfire", 85 | "filename" : "campfire-flames.gif", 86 | "description" : "fire" 87 | }, 88 | { 89 | "title" : "burning house", 90 | "filename" : "Animated-building-on-fire-burning-down.gif", 91 | "description" : "burning house" 92 | }, 93 | { 94 | "title" : "candles", 95 | "filename" : "candles-burning.gif", 96 | "description" : "candle flame night" 97 | }, 98 | { 99 | "title" : "soccer fail", 100 | "filename" : "soccer_fail.gif", 101 | "description" : "soccer football fail" 102 | }, 103 | { 104 | "title" : "soccer fail", 105 | "filename" : "fakesoccerfightvbzk.gif", 106 | "description" : "fake soccer" 107 | }, 108 | { 109 | "title" : "mermaid", 110 | "filename" : "2j3gsas.gif", 111 | "description" : "lady sea" 112 | }, 113 | { 114 | "title" : "Money Flip", 115 | "filename" : "Counting-money-animation.gif", 116 | "description" : "money" 117 | }, 118 | { 119 | "title" : "$100", 120 | "filename" : "Moving-picture-flipping-through-hundred-dollar-bills-gif-animation.gif", 121 | "description" : "money" 122 | }, 123 | { 124 | "title" : "parrot dance", 125 | "filename" : "pretty-birds-dancing-animated-gif.gif", 126 | "description" : "birds parrot" 127 | }, 128 | { 129 | "title" : "music notes", 130 | "filename" : "little_music_note.gif", 131 | "description" : "music notes" 132 | }, 133 | { 134 | "title" : "see no evil", 135 | "filename" : "animated-hear-see-speak-no-evil-moving-woman.gif", 136 | "description" : "evil" 137 | }, 138 | { 139 | "title" : "Obama Dancing", 140 | "filename" : "fgY3C.gif", 141 | "description" : "president dancing" 142 | }, 143 | { 144 | "title" : "Obama Skateboarding", 145 | "filename" : "Gif-Obama.gif", 146 | "description" : "skateboard obama change" 147 | }, 148 | { 149 | "title" : "Obama Headshake", 150 | "filename" : "Obama_hurr.gif", 151 | "description" : "obama head" 152 | }, 153 | { 154 | "title" : "happy birthday", 155 | "filename" : "Happy-birthday-Elvis-Weatercock-animated-gif.gif", 156 | "description" : "happy birthday" 157 | }, 158 | { 159 | "title" : "gears", 160 | "filename" : "gears-turning-slowly.gif", 161 | "description" : "gears machine" 162 | }, 163 | { 164 | "title" : "pizza dog", 165 | "filename" : "tCheZ.gif", 166 | "description" : "pizza dog" 167 | }, 168 | { 169 | "title" : "Dancing girl", 170 | "filename" : "Animated-dancing-girl-in-white-blouse.gif", 171 | "description" : "dancing woman" 172 | }, 173 | { 174 | "title" : "Pecs", 175 | "filename" : "Hunks.gif", 176 | "description" : "men muscles" 177 | }, 178 | { 179 | "title" : "crazy dance", 180 | "filename" : "Moving-animated-picture-of-dancin-dude.gif", 181 | "description" : "crazy dance" 182 | }, 183 | { 184 | "title" : "dancing baby", 185 | "filename" : "baby_dancing.gif", 186 | "description" : "dancing baby" 187 | }, 188 | { 189 | "title" : "Obama Money", 190 | "filename" : "Obama-bucks-animated-money-gif.gif", 191 | "description" : "Obama Money" 192 | }, 193 | { 194 | "title" : "banana fall", 195 | "filename" : "the-tables-have-turned.gif", 196 | "description" : "banana fall" 197 | }, 198 | { 199 | "title" : "kitten dance", 200 | "filename" : "Cute-little-kitty-wearing-blue-toque-dancing-to-and-fro-to-music.gif", 201 | "description" : "kitten dance" 202 | }, 203 | { 204 | "title" : "cheetah music", 205 | "filename" : "1684animalleopardheadph.gif", 206 | "description" : "cheetah music headphones" 207 | }, 208 | { 209 | "title" : "nyancat pastry", 210 | "filename" : "nyan-cat-animation-art.gif", 211 | "description" : "cat pastry" 212 | }, 213 | { 214 | "title" : "cat can", 215 | "filename" : "Cat-in-a-can1.gif", 216 | "description" : "cat trash hide" 217 | }, 218 | { 219 | "title" : "cat dog fight", 220 | "filename" : "gifki_012.gif", 221 | "description" : "cat dog fight" 222 | }, 223 | { 224 | "title" : "cat hello", 225 | "filename" : "oie_3052622KmCWMAJE (1).gif", 226 | "description" : "cat hello" 227 | }, 228 | { 229 | "title" : "turtle chasing dog", 230 | "filename" : "tortoise_chases_dog_s.gif", 231 | "description" : "turtle dog fear" 232 | }, 233 | { 234 | "title" : "dachshundt-on-record-player-turntable", 235 | "filename" : "dachshundt-on-record-player-turntable.gif", 236 | "description" : "dog music record spin" 237 | }, 238 | { 239 | "title" : "pancake dj", 240 | "filename" : "pamela-polishes-pancakes.gif", 241 | "description" : "pancake music bikini" 242 | }, 243 | { 244 | "title" : "dancing tigersuite", 245 | "filename" : "Dancing-tigers.gif", 246 | "description" : "dancing tiger costume" 247 | }, 248 | { 249 | "title" : "headchop", 250 | "filename" : "azn-girl-vs-cement.gif", 251 | "description" : "karate head destroy" 252 | }, 253 | { 254 | "title" : "forrest gump ping-pong", 255 | "filename" : "Moving-animated-picture-of-ping-pong-tournament.gif", 256 | "description" : "table tennis" 257 | }, 258 | { 259 | "title" : "pong", 260 | "filename" : "pong-animated-gif.gif", 261 | "description" : "pong computer game" 262 | }, 263 | { 264 | "title" : "question_mark", 265 | "filename" : "question_mark.gif", 266 | "description" : "question" 267 | }, 268 | { 269 | "title" : "Animated-swirling-question-mark-picture-moving", 270 | "filename" : "Animated-swirling-question-mark-picture-moving.gif", 271 | "description" : "question mystery" 272 | }, 273 | { 274 | "title" : "Muppet Question", 275 | "filename" : "mupper_question.gif", 276 | "description" : "question muppet answer" 277 | }, 278 | { 279 | "title" : "earth clouds", 280 | "filename" : "earth.gif", 281 | "description" : "earth clouds" 282 | }, 283 | { 284 | "title" : "moon rover", 285 | "filename" : "moon_rover.gif", 286 | "description" : "moon astronaut driving" 287 | }, 288 | { 289 | "title" : "chemicals", 290 | "filename" : "chemicals.gif", 291 | "description" : "chemicals experiment" 292 | }, 293 | { 294 | "title" : "nuke", 295 | "filename" : "nuke.gif", 296 | "description" : "bomb nuclear destroy" 297 | } 298 | ] 299 | } 300 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Ben Lucas 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenGif 2 | This project provides a basic search tool to help find and share Animated Gifs. 3 | The basic functionality is similar to exiting gif search engines like Tenor and Giphy. The hope is that messaging applications could use this tool as a secure open source, alternative to existing commercial gif search engines. This project is at a very basic level, but any interest or feedback is appreciated. 4 | A basic demo can be run at www.opengif.net. 5 | 6 | ## How to run it: 7 | Run `./setup.sh` to download and install the necessary data. This will download a set of word vectors from 8 | [Facebook's FastText Word Vectors](https://fasttext.cc/docs/en/english-vectors.html) and convert them into a dictionary. Other sources of word vectors could be used instead. The python script `prepare_data.py` can also be rerun to generate a larger or smaller dictionary of english words. 9 | 10 | #### Python Version (WIP as of 2020-05-20): 11 | Runs on Python 3.6, and requires Numpy & Flask. 12 | In the terminal run: 13 | ```bash 14 | cd python_prototype/ 15 | pip3 install -r requirements.txt 16 | python3 demo.py 17 | ``` 18 | Enter a search query and a description of the gif will come back. 19 | An preliminary Flask prototype for a server based solution can be found in `/python_prototype/main.py` and this will serve the site found in `/web_interface`. 20 | 21 | ## How it works: 22 | This algorithm relies on a semantic space representation of english tokens. The dot product is used to quickly calculate the distance to calculate the semantic similarity between a search phrase a database of gifs. A more sophisticated similarity ranking that can handle logical dis/conjunction is the next step. 23 | 24 | ## Contributing: 25 | This project is very new, and a lot needs to be done. Some things we need: 26 | 27 | 1. An improved website for demonstrating the basic functionality. 28 | 2. Adding gifs. The current demo has a very limited set of gifs. More gifs are needed to cover a greater range of uses. 29 | 3. Design/build an API for 3rd party use. 30 | 4. Improve the logic of the search algorithm to handle more complex queries (AND/OR). 31 | 5. Improve the performance of the search algorithm runs (possible switch to C++) 32 | -------------------------------------------------------------------------------- /prepare_data.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import io 3 | import json 4 | import pickle 5 | 6 | def LoadWordVectors(word_vector_datafile, n_words): 7 | f = io.open(word_vector_datafile, 'r', encoding='utf-8', newline='\n', errors='ignore') 8 | n, d = map(int, f.readline().split()) 9 | word_vector_dimension = d 10 | word_dict = {} 11 | if(n_words <= 0): 12 | n_words = n 13 | else: 14 | n_words = min(n, n_words) 15 | for i in range(n_words): 16 | line = f.readline() 17 | tokens = line.rstrip().split(' ') 18 | word_dict[tokens[0]] = [float(x) for x in tokens[1:]] 19 | return word_dict 20 | 21 | parser = argparse.ArgumentParser( 22 | description='Prepare Dictionary for word vectors') 23 | parser.add_argument('--word_count', dest='total_word_count', default=20000, type=int) 24 | args = parser.parse_args() 25 | 26 | total_word_count = args.total_word_count 27 | word_vector_datafile = './Data/WordVectors/word_vectors.vec' 28 | word_dict = LoadWordVectors(word_vector_datafile, total_word_count) 29 | 30 | with open('./Data/WordVectors/word_dict.pkl', 'wb') as f: 31 | pickle.dump(word_dict, f) 32 | -------------------------------------------------------------------------------- /python_prototype/GifFinder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Created on Sun Mar 11 13:11:32 2018 5 | 6 | @author: benjaminlucas 7 | """ 8 | import numpy as np 9 | import pickle 10 | import json 11 | 12 | class GifFinder(): 13 | def __init__(self, word_vector_datafile = '../Data/WordVectors/word_dict.pkl'): 14 | self.word_vector_dimension = 0 15 | self.total_word_count = 0 16 | self.LoadGifData() 17 | print('loading word vectors') 18 | self.word_dict = self.LoadWordVectors(word_vector_datafile) 19 | self.gif_matrix = self.GenerateGifMatrix() 20 | print('ready') 21 | 22 | def LoadGifData(self): 23 | f = open('../Data/Gif_Data/gif_data.json', 'r') 24 | json_data = json.load(f) 25 | gif_data = json_data['gifs'] 26 | self.gif_filenames = [gif['filename'] for gif in gif_data] 27 | self.gif_titles = [gif['title'] for gif in gif_data] 28 | self.gif_descriptions = [gif['description'] for gif in gif_data] 29 | 30 | def LoadWordVectors(self, word_vector_datafile): 31 | with open(word_vector_datafile, 'rb') as f: 32 | word_dict = pickle.load(f) 33 | self.total_word_count = len(word_dict.keys()) 34 | self.word_vector_dimension = len(next(iter(word_dict.values()))) 35 | return word_dict 36 | 37 | def GenerateGifMatrix(self): 38 | gif_matrix = np.zeros([len(self.gif_descriptions), self.word_vector_dimension]) 39 | for i, gif in enumerate(self.gif_descriptions): 40 | vec = self.MakePhraseVector(gif) 41 | gif_matrix[i,:] = vec 42 | return gif_matrix 43 | 44 | def MakePhraseVector(self, phrase): 45 | phrase = phrase.lower() 46 | stop_chars = [':', ';', '-',','] 47 | for c in stop_chars: 48 | phrase = phrase.replace(c,' ') 49 | words = phrase.lower().split(' ') 50 | words = [w for w in words if len(w) > 1 and w in self.word_dict] 51 | phrase_vec = [] 52 | for w in words: 53 | if(len(phrase_vec) == 0): 54 | phrase_vec = self.word_dict[w] 55 | else: 56 | phrase_vec = np.add(phrase_vec, self.word_dict[w]) 57 | if(len(words) > 0): 58 | phrase_vec_norm = phrase_vec/(np.linalg.norm(phrase_vec)) 59 | return phrase_vec_norm 60 | else: 61 | print('no words recognized in phrase: ' + phrase) 62 | phrase_vec = self.word_dict['question'] 63 | phrase_vec_norm = phrase_vec/(np.linalg.norm(phrase_vec)) 64 | return phrase_vec_norm 65 | 66 | def FindGif(self, search_phrase, n_results = 5): 67 | search_phrase_vector = self.MakePhraseVector(search_phrase) 68 | similarity = np.matmul(self.gif_matrix, np.transpose(search_phrase_vector)) 69 | sort_inds = np.flipud(np.argsort(similarity)) 70 | return list(sort_inds[0:n_results]) 71 | -------------------------------------------------------------------------------- /python_prototype/__pycache__/GifFinder.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bnlcas/OpenGif/43532b186444bd7c245168ee59f47d61c3519898/python_prototype/__pycache__/GifFinder.cpython-36.pyc -------------------------------------------------------------------------------- /python_prototype/demo.py: -------------------------------------------------------------------------------- 1 | from GifFinder import GifFinder 2 | 3 | gif_finder = GifFinder(word_vector_datafile = '../Data/WordVectors/word_dict.pkl') 4 | test_phrase = 'party' 5 | search_result_inds = gif_finder.FindGif(test_phrase) 6 | print('Sample search query: ' + test_phrase) 7 | for i in range(min(3, len(search_result_inds))): 8 | print('option ' + str(i + 1) + ': ' + gif_finder.gif_titles[search_result_inds[i]]) 9 | 10 | print('try another search query') 11 | while True: 12 | phrase = input() 13 | search_result_inds = gif_finder.FindGif(phrase) 14 | for i in range(min(3, len(search_result_inds))): 15 | print('option ' + str(i + 1) + ': ' + gif_finder.gif_titles[search_result_inds[i]]) 16 | -------------------------------------------------------------------------------- /python_prototype/main.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask import request 3 | from flask import render_template 4 | from GifFinder import GifFinder 5 | import json 6 | 7 | gif_finder = GifFinder(word_vector_datafile = '../Data/WordVectors/word_dict.pkl') 8 | app = Flask(__name__, template_folder='../web_interface', static_folder='../web_interface/static') 9 | #static_folder='web/static', 10 | #template_folder='web/templates' 11 | 12 | @app.before_request 13 | def before_request(): 14 | # When you import jinja2 macros, they get cached which is annoying for local 15 | # development, so wipe the cache every request. 16 | if 'localhost' in request.host_url or '0.0.0.0' in request.host_url: 17 | app.jinja_env.cache = {} 18 | 19 | 20 | @app.route('/query', methods = ['POST']) 21 | def query(): 22 | data = request.get_data() 23 | data_json = json.loads(data) 24 | search_phrase = data_json['search'] 25 | print(search_phrase) 26 | search_result_inds = gif_finder.FindGif(search_phrase) 27 | result = { 28 | 'results': [ 29 | { 30 | 'result_title': gif_finder.gif_titles[search_result_inds[0]], 31 | 'result_filename': gif_finder.gif_filenames[search_result_inds[0]] 32 | }, 33 | { 34 | 'result_title': gif_finder.gif_titles[search_result_inds[1]], 35 | 'result_filename': gif_finder.gif_filenames[search_result_inds[1]] 36 | }, 37 | { 38 | 'result_title': gif_finder.gif_titles[search_result_inds[2]], 39 | 'result_filename': gif_finder.gif_filenames[search_result_inds[2]] 40 | } 41 | ] 42 | } 43 | return json.dumps(result) 44 | 45 | @app.route("/") 46 | def index(): 47 | return render_template('opengif.html') 48 | 49 | if __name__ == '__main__': 50 | app.jinja_env.auto_reload = True 51 | app.config['TEMPLATES_AUTO_RELOAD'] = True 52 | app.config['TESTING'] = True 53 | app.run(debug=True) 54 | -------------------------------------------------------------------------------- /python_prototype/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask>=1.1.2 2 | numpy>=1.18.1 3 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | mkdir WordVectors 2 | curl https://dl.fbaipublicfiles.com/fasttext/vectors-english/wiki-news-300d-1M.vec.zip -o ./WordVectors/word_vectors.zip 3 | unzip ./WordVectors/word_vectors.zip -d ./Data/WordVectors/ 4 | rm ./Data/WordVectors/word_vectors.zip 5 | mv ./Data/WordVectors/wiki-news-300d-1M.vec ./Data/WordVectors/word_vectors.vec 6 | python3 prepare_data.py --word_count 20000 7 | -------------------------------------------------------------------------------- /web_interface/opengif.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |Type a search query
Press (ENTER) to see results.
10 | Search phrase:
11 |
12 |
Suggestions:
14 |