Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
TOuCAN/bin/pptx_writer_vTOuCAN.py
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
517 lines (480 sloc)
16.2 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
glob_dictionary = {} | |
glob_files = [] | |
glob_pictures = [] | |
glob_structure = [] | |
glob_layoutdict = {} | |
glob_slide_counter = 0 | |
glob_width = 0 | |
glob_height = 0 | |
glob_multipic = False | |
def sorted_nicely( l ): | |
convert = lambda text: int(text) if text.isdigit() else text | |
alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)] | |
return sorted(l, key = alphanum_key) | |
def create_layoutdict( presentationx ): | |
print(" -- scnanning existing layouts --") | |
global glob_layoutdict | |
layouts = presentationx.slide_layouts | |
max_layouts = len(layouts) | |
counter = 0 | |
for x in range(0,max_layouts): | |
glob_layoutdict[layouts[x].name] = counter | |
print(" -+ "+ str(counter) +": "+ layouts[x].name + " -") | |
counter = counter + 1 | |
print("") | |
def hex_cutter(font_color): | |
font_color = font_color.lstrip('#') | |
rgb_color = struct.unpack('BBB',font_color.decode('hex')) | |
return(rgb_color) | |
def format_text( run , part ): | |
font = run.font | |
try: | |
pt_size = part["font_size"] | |
font.size = Pt(pt_size) | |
except: | |
pass | |
try: | |
font_name = part["font_name"] | |
font.name = font_name | |
except: | |
pass | |
try: | |
font_color = part["font_color"] | |
rgb_color = hex_cutter(font_color) | |
val1 = rgb_color[0]; val2 = rgb_color[1]; val3 = rgb_color[2] | |
run.font.color.rgb = RGBColor(val1, val2, val3) | |
except: | |
pass | |
def format_shape( tx_box , part ): | |
try: | |
line_color = part["line_color"] | |
rgb_color = hex_cutter(line_color) | |
val1 = rgb_color[0]; val2 = rgb_color[1]; val3 = rgb_color[2] | |
tx_box.line.color.rgb = RGBColor(val1, val2, val3) | |
except: | |
pass | |
try: | |
fill_color = part["fill_color"] | |
rgb_color = hex_cutter(fill_color) | |
tx_box.fill.solid() | |
val1 = rgb_color[0]; val2 = rgb_color[1]; val3 = rgb_color[2] | |
tx_box.fill.fore_color.rgb = RGBColor(val1, val2, val3) | |
except: | |
pass | |
def add_text( tx_box , part , pic_count ): | |
tf = tx_box.text_frame | |
tf.word_wrap = True | |
input = part["input"] | |
if type(part["input"]) == int: | |
print(" -- connecting textbox with image name") | |
number = part["input"] | |
p = tf.paragraphs[0] | |
try: | |
if part["align"] == "c" or part["align"] == "center": | |
p.alignment = PP_ALIGN.CENTER | |
except: | |
pass | |
run = p.add_run() | |
try: | |
run.text = glob_pictures[number][pic_count] | |
except: | |
print(" >> Not able to connect textbox to a picture.") | |
try: | |
format_text(run, part) | |
except: | |
print(" >> Was not able to read the textbox format.") | |
p = tf.add_paragraph() | |
else: | |
text_list = part["input"] | |
for x in range(0,len(text_list)): | |
p = tf.paragraphs[x] | |
try: | |
if part["align"] == "c" or part["align"] == "center": | |
p.alignment = PP_ALIGN.CENTER | |
except: | |
pass | |
run = p.add_run() | |
run.text = text_list[x] | |
format_text(run, part) | |
p = tf.add_paragraph() | |
def get_value( string , entry ): | |
value = entry[string] | |
return(value) | |
def adjust_picheight( pic , necessary_height ): | |
store_height_1 = pic.height | |
pic.height = necessary_height | |
store_height_2 = pic.height | |
factor = float(store_height_2)/float(store_height_1) | |
pic.width = int(pic.width*factor) | |
return(pic.width) | |
def multipic_list_check( file_list ): | |
length = 0 | |
picamount = len(file_list[0]) | |
picsperslide = glob_structure[0]*glob_structure[1] # in multipic-mode, parts amount is equal on every layer | |
complete_slides = picamount / picsperslide | |
broken_slides = float(picamount) % picsperslide | |
if broken_slides != 0: | |
complete_slides = complete_slides + 1 | |
length = complete_slides | |
print("multipic:") | |
return(length) | |
def list_check( file_list ): | |
### file list = multi list of all pictures and textboxes. | |
### Check the longest of all part entries. | |
length = 0 | |
for entry in file_list: | |
if len(entry) > length: | |
length = len(entry) | |
print(" -- " + str(length) + " slides will be created") | |
return(length) | |
def path_and_file( part_input ): | |
split_list = part_input.split('/') | |
last_element = len(split_list) | |
regex = split_list[-1] | |
path = "" | |
for x in range(0,last_element-1): | |
path = path + split_list[x] + "/" | |
basic_tuple = (path, regex) | |
print(basic_tuple) | |
return(basic_tuple) | |
def get_files( entry, entrycount, args ): | |
print(" -- Working with JSON file entry no. " + str(entrycount+1)) | |
global glob_files | |
global glob_pictures | |
global glob_structure | |
global glob_multipic | |
part_count = 0 | |
### Check if layers are activated and existing. | |
layer_control = False | |
multipic_layer_control = False | |
try: | |
layers = entry["layers"] | |
layer_control = True | |
except: | |
pass | |
try: | |
multipic_layers = entry["multipic_layers"] | |
multipic_parts = entry["multipic_parts"] | |
multipic_input = entry["multipic_input"] | |
multipic_layer_control = True | |
except: | |
pass | |
if layer_control == True and multipic_layer_control == False: | |
### Filling the structure list (for example: [3, 4, 4, 4], 3 layers, 4 parts each layer) | |
glob_structure.append(len(layers)) | |
for layer in layers: | |
parts = layer["parts"] | |
glob_structure.append(len(parts)) | |
for part in parts: | |
if part["type"] == "picture": | |
basic_tuple = path_and_file(part["input"]) | |
temp_list = []; temp_pic_list = []; path = basic_tuple[0]; regex = basic_tuple[1]; sorting_list = [] | |
### Catching common path errors: | |
if path == "/" or path == "" or path == " ": | |
path_search = "." | |
else: | |
path_search = path | |
pattern = re.compile(regex) | |
try: | |
for file in os.listdir(path_search): | |
if pattern.search(file): | |
sorting_list.append(file) | |
####### Sorting the files after searching. | |
sorting_list = sorted_nicely(sorting_list) | |
########## | |
for e in sorting_list: | |
file_tuple = (path, e,) | |
temp_list.append(file_tuple) | |
temp_pic_list.append(e) | |
except: | |
print(" >> path or files not existing.") | |
if len(temp_list) == 0: | |
print(" >> Found 0 picture files for " + basic_tuple[1]) | |
else: | |
print(" -- Found " + str(len(temp_list)) + " picture files for " + basic_tuple[1]) | |
glob_files.append(temp_list) | |
glob_pictures.append(temp_pic_list) | |
if part["type"] == "textbox": | |
temp_list = [] | |
temp_list.append(part["input"]) | |
glob_files.append(temp_list) | |
### A layer amount is needed. If no layer is existing, glob_structure needs a zero.############# TODO: lfiles | |
elif multipic_layer_control == True: | |
glob_multipic = True | |
glob_structure.append(multipic_layers) | |
for layer in range(0,multipic_layers): | |
glob_structure.append(multipic_parts) | |
### Filling the structure list (for example: [3, 4, 4, 4], 3 layers, 4 pictures each layer) | |
basic_tuple = path_and_file(multipic_input) | |
temp_list = []; temp_pic_list = []; path = basic_tuple[0]; regex = basic_tuple[1]; sorting_list = [] | |
### Catching common path errors: | |
if args.flist is None: | |
if path == "/" or path == "" or path == " ": | |
path_search = "." | |
else: | |
path_search = path | |
pattern = re.compile(regex) | |
try: | |
for file in os.listdir(path_search): | |
if pattern.search(file): | |
sorting_list.append(file) | |
####### Sorting the files after searching. | |
sorting_list = sorted_nicely(sorting_list) | |
########## | |
for e in sorting_list: | |
file_tuple = (path, e,) | |
temp_list.append(file_tuple) | |
temp_pic_list.append(e) | |
print(temp_pic_list) | |
print(temp_list) | |
except: | |
print(" >> path or files not existing.") | |
else: | |
absPathList = args.flist.split(",") | |
absPathList = absPathList | |
for p in absPathList: | |
dname = os.path.dirname(p) +"/" | |
bname = os.path.basename(p) | |
file_tuple = (dname, bname,) | |
temp_list.append(file_tuple) | |
temp_pic_list.append(bname) | |
print(temp_pic_list) | |
print(temp_list) | |
############################################################################################### | |
if len(temp_list) == 0: | |
print(" >> Found 0 picture files for " + basic_tuple[1]) | |
else: | |
print(" -- Found " + str(len(temp_list)) + " picture files for " + basic_tuple[1]) | |
glob_files.append(temp_list) | |
glob_pictures.append(temp_pic_list) | |
else: | |
### 1 list with 1 item must be added: Entry amount should be >0, to create content free slides (like title slides) too. | |
glob_files.append(['None']) | |
glob_structure.append(0) | |
def write_slides( presentation , entry , entrycount ): | |
global glob_slide_counter | |
file_list = glob_files | |
layeramount = glob_structure[0] | |
layout_number = 1 | |
try: | |
layout_number = entry["layout"] | |
except: | |
pass | |
try: | |
layout_number = glob_layoutdict[layout_number] | |
except: | |
pass | |
head_height = 0.15 | |
try: | |
head_height = entry["head_height"] | |
except: | |
pass | |
if layeramount != 0: | |
layer_max_height_factor_list = [] | |
layer_max_height_list = [] | |
for layer in range(0,layeramount): | |
try: | |
factor = entry["layers"][layer]["size"] | |
layer_max_height_factor_list.append(factor) | |
layer_max_height_list.append(int((glob_height-glob_height*head_height)*factor)) | |
except: | |
factor = 1/float(layeramount) | |
layer_max_height_factor_list.append(factor) | |
layer_max_height_list.append(int((glob_height-glob_height*head_height)*factor)) | |
if glob_multipic == False: | |
max_fileamount = list_check(file_list) | |
else: | |
max_fileamount = multipic_list_check(file_list) | |
print(" -- " + str(layeramount) + " layers for slide found.") | |
text_count = 0 #outside of for x in range, because needs to be stored over slides away. Stays 0, text always the same | |
pic_count = 0 #outside of for x in range, because needs to be stored over slides away. | |
for x in range(0,max_fileamount): | |
slidex = presentation.slides.add_slide(presentation.slide_layouts[layout_number]) | |
placeholders = slidex.placeholders | |
glob_slide_counter = glob_slide_counter + 1 | |
print(" ++ Adding slide no. " + str(glob_slide_counter)) | |
try: | |
note_text_list = entry["notes"] | |
notes_slide = slidex.notes_slide | |
text_note = notes_slide.notes_text_frame | |
for x in range(0,len(note_text_list)): | |
p = text_note.paragraphs[x] | |
run = p.add_run() | |
run.text = note_text_list[x] | |
p = text_note.add_paragraph() | |
except: | |
pass | |
try: | |
title_text = entry["title"] | |
for ph in placeholders: | |
if "Title" in ph.name: | |
tf = ph.text_frame | |
tf.vertical_anchor = MSO_ANCHOR.MIDDLE | |
p = tf.paragraphs[0] | |
run = p.add_run() | |
run.text = title_text | |
except: | |
pass | |
control = False | |
try: | |
texts_lists = entry["texts"] | |
control = True | |
except: | |
pass | |
if control == True: | |
text_lists = entry["texts"] | |
ph_counter = 0 | |
for ph in placeholders: | |
if "Text Placeholder" in ph.name: | |
try: | |
add_text(ph, text_lists[ph_counter], 0) #number must be given because of class | |
format_shape(ph, text_lists[ph_counter]) | |
except: | |
pass | |
finally: | |
ph_counter = ph_counter + 1 | |
file_count = 0 | |
if layeramount != 0: | |
factor_layer = 0 | |
for layer in range(0,layeramount): | |
factor_part = 0 | |
partsamount = glob_structure[layer+1] #layer amount at 0, part amounts in glob_structure start at position 1 | |
for y in range(0,partsamount): | |
if glob_multipic == False: | |
parts = entry["layers"][layer]["parts"] | |
try: | |
frame_value = get_value("frame", parts[y]) | |
frame = frame_value * glob_width | |
except: | |
frame = 0 | |
try: | |
size_part = parts[y]["size"] | |
except: | |
size_part = 1/float(partsamount) | |
left = factor_part*glob_width + frame | |
top = glob_height*head_height + factor_layer*(glob_height-glob_height*head_height) + frame #headline protection + 0 probably + frame distance | |
width = glob_width*size_part - (2*frame) | |
if width < 0: | |
width = glob_width*size_part | |
print(" >> width became negative. Deactivated (some) frames") | |
height = layer_max_height_list[layer] - (2*frame) | |
if height < 0: | |
height = glob_width*size_part | |
print(" >> height became negative. Deactivated (some) frames") | |
# print("left: " + str(left)); print("top: " + str(top)); print("width: " + str(width)); print("height: " + str(height)); print("(frame*2: " + str(frame*2) + ")"); print(" ") | |
if parts[y]["type"] == "picture": | |
try: | |
img_path = file_list[file_count][pic_count][0] + file_list[file_count][pic_count][1] | |
pic = slidex.shapes.add_picture(img_path, left, top, width = width) | |
necessary_height = int(layer_max_height_list[layer] -(frame*2)) | |
if pic.height > necessary_height: | |
pic.width = adjust_picheight(pic, necessary_height) | |
pic.height = necessary_height | |
if pic.width < (glob_width*size_part - (2*frame)): | |
half_space_width = (glob_width*size_part - (2*frame))/2 | |
half_pic_width = pic.width/2 | |
pic.left = int((half_space_width - half_pic_width) + factor_part*glob_width) | |
except: | |
print(" >> Failed to place the picture!") | |
if parts[y]["type"] == "textbox": | |
tx_box = slidex.shapes.add_textbox(left, top, width, height) | |
format_shape(tx_box, parts[y]) | |
add_text(tx_box, parts[y], pic_count) | |
file_count = file_count + 1 | |
factor_part = factor_part + size_part | |
else: # if multipic is enabled: | |
try: | |
frame_value = entry["multipic_frame"] | |
frame = frame_value * glob_width | |
except: | |
frame = 0 | |
size_part = 1/float(partsamount) | |
left = factor_part*glob_width + frame | |
top = glob_height*head_height + factor_layer*(glob_height-glob_height*head_height) + frame #headline protection + 0 probably + frame distance | |
width = glob_width*size_part - (2*frame) | |
if width < 0: | |
width = glob_width*size_part | |
print(" >> width became negative. Deactivated (some) frames") | |
height = layer_max_height_list[layer] - (2*frame) | |
if height < 0: | |
height = glob_width*size_part | |
print(" >> height became negative. Deactivated (some) frames") | |
try: | |
img_path = file_list[file_count][pic_count][0] + file_list[file_count][pic_count][1] | |
pic = slidex.shapes.add_picture(img_path, left, top, width = width) | |
necessary_height = int(layer_max_height_list[layer] -(frame*2)) | |
if pic.height > necessary_height: | |
pic.width = adjust_picheight(pic, necessary_height) | |
pic.height = necessary_height | |
if pic.width < (glob_width*size_part - (2*frame)): | |
half_space_width = (glob_width*size_part - (2*frame))/2 | |
half_pic_width = pic.width/2 | |
pic.left = int((half_space_width - half_pic_width) + factor_part*glob_width) | |
except: | |
print(" >> Failed to place the picture!") | |
pic_count = pic_count + 1 | |
factor_part = factor_part + size_part | |
factor_layer = factor_layer + layer_max_height_factor_list[layer] | |
if glob_multipic == False: | |
pic_count = pic_count + 1 | |
else: | |
pass | |
def argparsefunc(): | |
parser = argparse.ArgumentParser() | |
parser.add_argument("jfile", type=str, help="Give the JSON file name.") | |
parser.add_argument("--pptx", type=str, help="Give a pptx file template (check for layout names).") | |
parser.add_argument("--output", type=str, help="Give a file output name.") | |
parser.add_argument("--flist", type=str, help="list of file paths") | |
try: | |
args = parser.parse_args() | |
return(args) | |
except SystemError: | |
parser.print_help() | |
exit() | |
def main(): | |
global glob_dictionary; global glob_width; global glob_height; global glob_structure; global glob_files; global glob_pictures | |
args = argparsefunc() | |
try: | |
output = args.output + ".pptx" | |
except: | |
output = args.pptxfile + "_generated.pptx" | |
starttime = datetime.datetime.now() | |
print(starttime.strftime("\n >> %Y-%m-%d %H:%M:%S\n")) | |
with open(args.jfile, 'r+') as json_data: | |
glob_dictionary = json.load(json_data) | |
### Core functions. | |
presentation = Presentation(args.pptx) | |
create_layoutdict(presentation) | |
layouts = presentation.slide_layouts | |
glob_width = presentation.slide_width | |
glob_height = presentation.slide_height | |
entrycount = 0 | |
for entry in glob_dictionary["slides"]: | |
get_files(entry, entrycount, args) | |
write_slides(presentation, entry, entrycount) | |
glob_structure = [] | |
glob_files = [] | |
glob_pictures = [] | |
entrycount = entrycount + 1 | |
presentation.save(output) | |
##### | |
endtime = datetime.datetime.now() | |
print(endtime.strftime("\n >> %Y-%m-%d %H:%M:%S\n")) | |
print(" -- FINISHED --") | |
print(" (" + output + ")") | |
if __name__ == "__main__": | |
from pptx.enum.text import MSO_ANCHOR | |
from pptx.enum.text import PP_ALIGN | |
from pptx.dml.color import RGBColor | |
from pptx import Presentation | |
from pptx.util import Inches | |
from pptx.util import Pt | |
import datetime | |
import argparse | |
import numbers | |
import struct | |
import json | |
import re | |
import os | |
main() |