randstrip.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. #!/usr/bin/python3.6
  2. from PIL import Image
  3. from PIL import ImageFont
  4. from PIL import ImageDraw
  5. import csv
  6. import random
  7. import os
  8. import json
  9. fileDir = os.path.dirname(os.path.abspath(__file__))
  10. def replaceText(text,config):
  11. """This function replace $WILDCARD with a word found in subs.csv
  12. subs.csv definition is 1st colum $WILDCARD, subsequent columns, possible values (chosen at random), delimiter is ;"""
  13. with open(config["csvLocation"]+"/"+config["csvSubs"]) as subs:
  14. csvReader = csv.reader(subs,delimiter=";")
  15. for row in csvReader:
  16. if text.find(row[0]) != -1:
  17. text = text.replace(row[0],row[random.randint(1,len(row)-1)],1)
  18. return text
  19. print("Parsing error \""+text+"\" not found")
  20. quit()
  21. def fetchText(indText,config):
  22. """This function fetch the text for the image with two characters
  23. rtext.csv definition is: 1st column the name of the file (i.e. B001.png), 2nd number of actors (at the moment
  24. they are limited to two; then a couple of columns or each actor with x and y coord of the strings; after the coords the outcomes,
  25. one column for each actor
  26. Delimiter is ; and line feeds @, if there aren't any options, it returns 0 (no text)
  27. It returns two arrays, coords is a tuple (x,y) and result is the outcome"""
  28. with open(config["csvLocation"]+"/"+config["csvSpeech"]) as rtext:
  29. csvReader = csv.reader(rtext,delimiter=';')
  30. for row in csvReader:
  31. if row[0]==indText:
  32. noActors = int(row[1])
  33. if noActors == 0:
  34. return 0
  35. else:
  36. firstElement = 2+(noActors*2)
  37. lastElement = len(row)-(noActors-1)
  38. randQuote = random.randrange(firstElement,lastElement,noActors)
  39. coords = []
  40. result = []
  41. for x in range(0,noActors):
  42. coords.append((row[2+x*2],row[3+x*2]))
  43. result.append(row[randQuote+x])
  44. return coords,result
  45. def fetchVign(config):
  46. """This functions fetch an image, randomly, chosen from a markov tree defined in ram.csv
  47. ram.csv definition is: 1st column the name of the image (without extension), subsequent columns, possible outcomes chosen randomly
  48. It returns an array with the file names"""
  49. starts = []
  50. startdest = []
  51. nvign = 0
  52. currVign = "000"
  53. story = []
  54. with open(config["csvLocation"]+"/"+config["csvTree"]) as ram:
  55. csvReader = csv.reader(ram)
  56. for row in csvReader:
  57. starts.append(row[0])
  58. startdest.append(row)
  59. while nvign <100:
  60. story.append(startdest[starts.index(currVign)][random.randint(1,len(startdest[starts.index(currVign)])-1)])
  61. currVign = story[nvign]
  62. if currVign == "END":
  63. return story
  64. story[nvign]+=".png"
  65. nvign +=1
  66. print("tree with no END")
  67. quit()
  68. def addThing(indVign,config):
  69. """This function adds a small image (object) to a larger image
  70. obj.csv definition is: name of the image (i.e. A001.png), x-coord, y-coord, subsequent columns possible outcomes
  71. It returns a tuple (object file name, x, y)"""
  72. objects = list()
  73. with open(config["csvLocation"]+"/"+config["csvObj"]) as obj:
  74. csvReader = csv.reader(obj)
  75. for row in csvReader:
  76. if row[0] == indVign:
  77. objects.append( (row[random.randint(3,len(row)-1)],row[1],row[2] ))
  78. return objects
  79. def fetchAltText(config,csvFile,indVign):
  80. """Function to fetch the correct alt text for the image"""
  81. if os.path.exists(config["csvLocation"]+"/"+config["csvAltText"]):
  82. with open(csvFile) as alt_text_csv:
  83. csvReader = csv.reader(alt_text_csv,delimiter=";")
  84. for row in csvReader:
  85. if row[0] == indVign:
  86. return row[1]
  87. else:
  88. return ""
  89. def writeStrip(story,config):
  90. """This function creates the strip returning an image object that could be saved or viewed. It takes an array with filenames as parameter
  91. The first image is always 000, then appends to strip the files, then decorates it fetching text and adding objects. If the object is an R, then
  92. repeats the last object. It also generates alt text if setted correctly in the config file."""
  93. strip = []
  94. alt_text = ""
  95. for indVign in story:
  96. try:
  97. vign = Image.open(config["imagesLocation"]+"/"+indVign).convert('RGBA')
  98. addtext = ImageDraw.Draw(vign)
  99. fnt = ImageFont.truetype(config["font"],config["fontSize"])
  100. text_vign_ind = fetchText(indVign,config)
  101. ind_text_vign = 1
  102. alt_text += fetchAltText(config,config["csvLocation"]+"/"+config["csvAltText"],indVign)+"\n"
  103. if text_vign_ind!=0:
  104. try:
  105. for x in range(len(text_vign_ind[0])):
  106. text_vign = text_vign_ind[1][x]
  107. try:
  108. while text_vign.find('$') != -1:
  109. text_vign = replaceText(text_vign,config)
  110. except AttributeError as err:
  111. print("Problem parsing:")
  112. print(text_vign_ind)
  113. print(err)
  114. quit()
  115. while '$'+str(ind_text_vign) in alt_text:
  116. alt_text = alt_text.replace('$'+str(ind_text_vign),text_vign.replace('@'," "))
  117. ind_text_vign += 1
  118. text_vign = text_vign.replace('@','\n')
  119. addtext.multiline_text((int(text_vign_ind[0][x][0]),int(text_vign_ind[0][x][1])),text_vign,fill="#000000",font=fnt,align="center")
  120. text_vign = ""
  121. except TypeError:
  122. print("Problem finding text for:")
  123. print(indVign)
  124. quit()
  125. obj_list = addThing(indVign,config)
  126. if obj_list!=0:
  127. for obj in obj_list:
  128. if obj[0] == 'R':
  129. objImg = Image.open(config["imagesLocation"]+"/"+prevObj[0])
  130. else:
  131. prevObj = obj
  132. objImg = Image.open(config["imagesLocation"]+"/"+obj[0])
  133. try:
  134. vign.paste(objImg,(int(obj[1]),int(obj[2])),objImg)
  135. except ValueError:
  136. vign.paste(objImg,(int(obj[1]),int(obj[2])))
  137. if "$0" in alt_text:
  138. alt_text = alt_text.replace("$0",fetchAltText(config,config["csvLocation"]+"/"+config["csvAltText"],obj_list[0][0])+" ")
  139. strip.append(vign)
  140. except FileNotFoundError:
  141. pass
  142. image = Image.new('RGBA',(config["xSize"],config["ySize"]))
  143. xshift = yshift = 0
  144. pprow = config["panelsPerRow"]
  145. for vign in strip:
  146. image.paste(vign,(xshift,yshift))
  147. pprow -= 1
  148. if pprow > 0:
  149. xshift += config["panelLength"]
  150. else:
  151. xshift = 0
  152. yshift += config["panelHeight"]
  153. pprow = config["panelsPerRow"]
  154. ImageDraw.Draw(image).rectangle([0,0,config["xSize"]-1,config["ySize"]-1], fill=None, outline="black", width=1)
  155. return image,alt_text
  156. def createStrip(config,specialPlatform="",altTextRequested=False):
  157. """Create strip and save it, if altTextRequested is True then returns also the alt text
  158. createStrip(str path/filename)"""
  159. try:
  160. story = fetchVign(config)
  161. finalStrip,altText = writeStrip(story,config)
  162. if specialPlatform == "android":
  163. return finalStrip
  164. else:
  165. finalStrip.save(config["saveLocation"]+config["filename"])
  166. if altTextRequested:
  167. return 0,altText
  168. else:
  169. return 0
  170. except Exception as err:
  171. return err
  172. def readConfig(profile=False,platform=False):
  173. """Read configuration file"""
  174. try:
  175. with open(fileDir+"/config.json") as f:
  176. config = json.load(f)
  177. except IOError:
  178. print("config.json not found")
  179. return False
  180. if not(profile):
  181. profile = config["defaultProfile"]
  182. else:
  183. profile = profile[0]
  184. try:
  185. checkProfile = config[profile]
  186. except KeyError:
  187. print("Profile "+profile+" not found")
  188. profile = config["defaultProfile"]
  189. saveLocation = checkLocal(config[profile]["saveLocation"])
  190. imagesLocation = checkLocal(config[profile]["imagesLocation"])
  191. csvLocation = checkLocal(config[profile]["csvLocation"])
  192. csvTree = config[profile]["csvTree"]
  193. csvSpeech = config[profile]["csvSpeech"]
  194. csvSubs = config[profile]["csvSubs"]
  195. csvObj = config[profile]["csvObj"]
  196. csvAltText = config[profile]["csvAltText"]
  197. font = checkLocal(config[profile]["font"])
  198. fontSize = int((config[profile]["fontSize"]))
  199. xSize = config[profile]["xSize"]
  200. ySize = config[profile]["ySize"]
  201. panelLength = config[profile]["panelLength"]
  202. panelHeight = config[profile]["panelHeight"]
  203. pprow = config[profile]["panelsPerRow"]
  204. if platform:
  205. token = checkLocal(config[profile][platform]["token"])
  206. filename = checkLocal(config[profile][platform]["filename"])
  207. try:
  208. text = config[profile][platform]["text"]
  209. except KeyError:
  210. text = False
  211. return {"saveLocation":saveLocation,"imagesLocation":imagesLocation,"csvLocation":csvLocation,"fontSize":fontSize,"font":font,"token":token,"filename":filename,"xSize":xSize,"ySize":ySize,"panelLength":panelLength,"panelHeight":panelHeight,"csvTree":csvTree,"csvSpeech":csvSpeech,"csvSubs":csvSubs,"csvObj":csvObj,"csvAltText":csvAltText,"text":text,"panelsPerRow":pprow}
  212. filename = config[profile]["filename"]
  213. return {"saveLocation":saveLocation,"imagesLocation":imagesLocation,"csvLocation":csvLocation,"fontSize":fontSize,"font":font,"filename":filename,"xSize":xSize,"ySize":ySize,"panelLength":panelLength,"panelHeight":panelHeight,"csvTree":csvTree,"csvSpeech":csvSpeech,"csvSubs":csvSubs,"csvObj":csvObj,"csvAltText":csvAltText,"panelsPerRow":pprow}
  214. def checkLocal(directory):
  215. """Checks if it's a relative or absolute path"""
  216. if directory[0] == ".":
  217. return fileDir + directory[1:]
  218. else:
  219. return directory
  220. if __name__ == "__main__":
  221. import argparse
  222. parser = argparse.ArgumentParser()
  223. parser.add_argument('-s','--story',metavar='story',default='',nargs=4,help='story = image file name')
  224. parser.add_argument('-a','--a4',default=False,action='store_true',help='print on an A4 in PDF, needs -o output, disables -x xsize')
  225. parser.add_argument('-m','--multiple',metavar='multiple',default=[1],nargs=1,type=int,help='multiple output (int >0), if no output -o specified, it just tests the stories')
  226. parser.add_argument('-x','--xsize',metavar='xsize',default=0,type=int,nargs=1,help='output image width')
  227. parser.add_argument('-y','--ysize',metavar='ysize',default=0,type=int,nargs=1,help='output image height')
  228. parser.add_argument('-p','--profile',metavar='profile',default="",type=str,nargs=1,help='select a profile registered in config.json, if it doesn\'t exist it will use the default one')
  229. parser.add_argument('-o','--output',metavar='output',const=True,default=False,nargs="?",help='output file, if name not specified, default path will be used')
  230. parser.add_argument('-r','--pprows',metavar='panelsPerRow',default=4,type=int,nargs=1,help='number of panels per row, default is 4')
  231. args = parser.parse_args()
  232. if args.multiple[0] <= 0: #Wrong multiple choice
  233. quit()
  234. config = readConfig(profile=args.profile)
  235. if args.output == True: #Output on but no filename specified
  236. fileName = config["saveLocation"]+config["filename"]
  237. elif type(args.output) == str: #Output specified
  238. fileName = args.output
  239. pdfs = list() #Prepare a list for the PDF
  240. for ist in range(0,args.multiple[0]):
  241. if (args.story == ''): #No story specified
  242. story = fetchVign(config)
  243. else:
  244. story = [] #Story specified
  245. for x in args.story:
  246. story.append(x)
  247. finalStrip,alt_text = writeStrip(story,config)
  248. if args.a4: #Prints a PDF
  249. finalStrip = finalStrip.resize((2249,516))
  250. pdfs.append(finalStrip)
  251. else:
  252. if args.xsize != 0: #Resize specified
  253. finalStrip = finalStrip.resize((args.xsize[0],int(args.xsize[0]/2400*500)))
  254. if args.multiple[0] == 1: #No multiple selected
  255. if args.output == False:
  256. finalStrip.show()
  257. print(alt_text)
  258. else:
  259. finalStrip.save(fileName)
  260. else: #Multiple selected
  261. if args.output == False:
  262. print(story,alt_text)
  263. else:
  264. finalStrip.save(fileName + str(ist).zfill(3) + ".png")
  265. if args.a4:
  266. ypos = 100
  267. nopage = 0
  268. if args.output == False:
  269. print("Output not specified")
  270. quit()
  271. pagePdf = list()
  272. for pag in range(0,int((args.multiple[0]-1)/6+1)):
  273. image = Image.new('RGB',(2479,3508),(255,255,255))
  274. fnt = ImageFont.truetype(config["font"],config["fontSize"])
  275. ImageDraw.Draw(image).text((2479 - 500, 3508 - 200),"https://github.com/oloturia/fumcaso",fill="black",font=fnt)
  276. pagePdf.append(image)
  277. for ist,strip_num in enumerate(range(0,args.multiple[0])):
  278. pagePdf[nopage].paste(pdfs[strip_num],box=(110,ypos))
  279. ypos += 516 + 20
  280. if ypos > 3508-569:
  281. ypos = 100
  282. nopage += 1
  283. if fileName[len(fileName)-4:] != ".pdf":
  284. fileName += ".pdf"
  285. pagePdf[0].save(fileName, resolution=300, save_all=True, append_images=pagePdf[1:])