├── dankcli ├── __init__.py ├── fonts │ ├── arial.ttf │ └── HelveticaNeue.ttf ├── functions.py └── __main__.py ├── MANIFEST.in ├── .gitignore ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── setup.py ├── LICENSE └── README.md /dankcli/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | build/ 3 | dankcli.egg-info/ 4 | dankcli/templates/ -------------------------------------------------------------------------------- /dankcli/fonts/arial.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sggts04/dankcli/HEAD/dankcli/fonts/arial.ttf -------------------------------------------------------------------------------- /dankcli/fonts/HelveticaNeue.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sggts04/dankcli/HEAD/dankcli/fonts/HelveticaNeue.ttf -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Exact stuff you did: 15 | 1. Original image 16 | 2. Exact command typed 17 | 18 | **Expected behavior** 19 | A clear and concise description of what you expected to happen. 20 | 21 | **Screenshots** 22 | What final image came out to be. 23 | 24 | **Setup in which bug happened:** 25 | - OS 26 | - Which command line/ terminal used(sometimes cmd on windows can be problematic) 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | def readme(): 4 | with open('README.md') as f: 5 | return f.read()[309:] 6 | 7 | setup(name='dankcli', 8 | version='0.5.8', 9 | description='CLI Meme Generator to automatically add whitespace and text to top', 10 | long_description=readme(), 11 | long_description_content_type='text/markdown', 12 | keywords='dankcli dank meme memegenerator memes generator pillow dankmemes', 13 | url='https://github.com/sggts04/dankcli', 14 | author='Shreyas Gupta', 15 | author_email='technology.shreyas@gmail.com', 16 | license='MIT', 17 | packages=['dankcli'], 18 | install_requires=[ 19 | 'pillow', 20 | ], 21 | package_data={ 22 | 'dankcli': ['fonts/*.ttf'], 23 | }, 24 | zip_safe=False) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Shreyas Gupta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dankcli 2 | [![PyPI version](https://img.shields.io/pypi/v/dankcli.svg?label=PyPI)](https://pypi.org/project/dankcli/) 3 | [![Python Version](https://img.shields.io/badge/Python-3.6%2B-blue.svg)](https://www.python.org/downloads/) 4 | [![Downloads](https://pepy.tech/badge/dankcli)](https://pepy.tech/project/dankcli) 5 | 6 | dankcli is a CLI Image Captioning Tool or Meme Generator which automatically adds white space and text to the top of your image. 7 | 8 | ## Installation 9 | 10 | ```bash 11 | $ pip install dankcli 12 | ``` 13 | 14 | ## Usage 15 | 16 | ```bash 17 | $ python -m dankcli "path/to/image" "Meme text you want to add" [-f "final_image_name_without_extension"] 18 | ``` 19 | 20 | The text gets automatically wrapped according to width of image but you can also have intentional \n in your text. 21 | The image is saved in the current folder with the name as the current date and time, the name can be changed with the optional `-f` or `--filename` argument, specifying a file name without the file extension. 22 | 23 | ## Example 24 | 25 | #### Example 1 (showing \n functionality) 26 | ```bash 27 | $ python -m dankcli "templates/yesbutno.jpg" "Mom at 2am: Are you awake?\n\nMe:" 28 | ``` 29 | turns this 30 | 31 | ![](https://i.imgur.com/nW3XPkF.jpg) 32 | 33 | to this 34 | 35 | ![](https://i.imgur.com/h6qgp9m.png) 36 | 37 | #### Example 2 (showing auto textwrap) 38 | ```bash 39 | $ python -m dankcli "mymemes/helpmeme.jpg" "When you make a meme generator but now you can't stop making memes" 40 | ``` 41 | turns this 42 | 43 | ![](https://i.imgur.com/6CDBFwF.jpg) 44 | 45 | to this 46 | 47 | ![](https://i.imgur.com/lSBUfNb.png) 48 | 49 | -------------------------------------------------------------------------------- /dankcli/functions.py: -------------------------------------------------------------------------------- 1 | from PIL import Image, ImageFont, ImageDraw 2 | import math 3 | import os 4 | import datetime 5 | 6 | TOP_PADDING = 10 7 | BOTTOM_PADDING = 10 8 | MINIMUM_FONT_SIZE = 13 # Lower Bound for font size 9 | HW_ASPECT_RATIO_THRESHOLD = 1.666 # To check if image is highly vertical 10 | WIDTH_PADDING = 5 # To make sure text isn't at complete edge 11 | 12 | def getFontSize(img): 13 | width, height = img.size[0], img.size[1] 14 | tempSize = max(math.floor(height/13), MINIMUM_FONT_SIZE) 15 | if tempSize==MINIMUM_FONT_SIZE or not height/width >= HW_ASPECT_RATIO_THRESHOLD: 16 | # Horizontal or Lower-Bound 17 | return tempSize 18 | else: 19 | # Vertical 20 | return math.floor(tempSize/1.5) 21 | 22 | def getTopLeftCorner(draw, lines, font, img): 23 | # Align according to longest line 24 | line = max(lines.split('\n'), key= lambda x: font.getsize(x)[0]) 25 | w= draw.textsize(line, font=font)[0] 26 | W = img.size[0] 27 | # Center horizontally, top vertically 28 | return ((W-w)/2, TOP_PADDING) 29 | 30 | # Text wrapper function to wrap text to new line if line gets longer than image width 31 | def textWrap(text, font, max_width): 32 | lines = [] 33 | 34 | if font.getsize(text)[0] < max_width: 35 | lines.append(text) 36 | else: 37 | # split the line by spaces to get words 38 | words = text.split(' ') 39 | i = 0 40 | # append every word to a line while line's width is shorter than image width 41 | while i < len(words): 42 | line = '' 43 | while i < len(words) and font.getsize(line + words[i])[0] < max_width - WIDTH_PADDING: 44 | line = line + words[i] + " " 45 | i += 1 46 | if not line: 47 | line = words[i] 48 | i += 1 49 | # when the line gets longer than the max width do not append the word, 50 | # add the line to the lines array 51 | lines.append(line) 52 | return '\n'.join(lines) 53 | 54 | def getWhiteSpaceHeight(lines, font): 55 | lineNos = len(lines.split('\n')) 56 | heightPerLine = font.getsize(lines.split('\n')[0])[1] 57 | # + 20 for 10 padding each for both top and bottom 58 | return heightPerLine*lineNos + TOP_PADDING + BOTTOM_PADDING 59 | 60 | def getFileName(): 61 | currentDateTime = str(datetime.datetime.now()).replace(" ", "").replace("-", "").replace(":", "").replace(".", "") 62 | return currentDateTime -------------------------------------------------------------------------------- /dankcli/__main__.py: -------------------------------------------------------------------------------- 1 | from PIL import Image, ImageFont, ImageDraw 2 | from .functions import getFontSize, getTopLeftCorner, getWhiteSpaceHeight, textWrap, getFileName 3 | import os 4 | import argparse 5 | import sys 6 | 7 | # Parsing Arguments to get Image Path and Meme Text 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument("img", help="relative path to image", 10 | type=str) 11 | parser.add_argument("text", help="text to put above image", 12 | type=str) 13 | parser.add_argument("-f", "--filename", help="file name for final image without extension", 14 | type=str) 15 | args = parser.parse_args() 16 | 17 | # Checking for JPEG/JPG Image to make sure to later save it as RGB and not RGBA 18 | args_img_lower = args.img.lower() 19 | isJPEG = True if (".jpg" in args_img_lower or ".jpeg" in args_img_lower) else False 20 | 21 | whiteColor = "rgb(255, 255, 255)" 22 | blackColor = "rgb(0, 0, 0)" 23 | 24 | # Check if specified image exists 25 | try: 26 | img_path = os.path.expanduser(args.img) 27 | img = Image.open(img_path) 28 | except Exception as e: 29 | print("Specified image doesn't exist or can't be opened") 30 | sys.exit(2) 31 | 32 | Width, Height = img.size 33 | line = args.text.replace("\\n", "\n") 34 | font_path = os.path.join(os.path.dirname(__file__), "fonts", "arial.ttf") 35 | font = ImageFont.truetype(font_path, size=getFontSize(img)) 36 | 37 | # Check if intentional line-breaks done 38 | if "\n" in line: 39 | # If yes, individually text wrap every line 40 | linesList = [] 41 | intentionalNewLines = line.split("\n") 42 | for intentionalNewLine in intentionalNewLines: 43 | linesList.append(textWrap(intentionalNewLine, font, Width)) 44 | lines = "\n".join(linesList) 45 | else: 46 | # If not, just text wrap on entire thing 47 | lines = textWrap(line, font, Width) 48 | 49 | imageWithWhiteSpace = Image.new("RGBA",( Width, Height + getWhiteSpaceHeight(lines, font) ),whiteColor) 50 | imageWithWhiteSpace.paste(img, (0, getWhiteSpaceHeight(lines, font))) 51 | draw = ImageDraw.Draw(imageWithWhiteSpace) 52 | 53 | draw.text(getTopLeftCorner(draw, lines, font, imageWithWhiteSpace), lines, fill=blackColor, font=font, align="left") 54 | 55 | # Get name of new image for saving 56 | newName = os.path.expanduser(args.filename) if args.filename else getFileName() 57 | 58 | if isJPEG: 59 | rgbImage = imageWithWhiteSpace.convert("RGB") 60 | rgbImage.save(f"{newName}.jpg") 61 | print("Saved as " + f"{newName}.jpg") 62 | else: 63 | imageWithWhiteSpace.save(f"{newName}.png") 64 | print("Saved as " + f"{newName}.png") 65 | --------------------------------------------------------------------------------