# THIS FILE IS A PART OF VCStudio # PYTHON 3 import os import datetime import json # This is for the export. We need a window for it. Maybe I will make my own. import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk import zipfile import re from settings import talk from studio import checklist #import checklist def get_legacy(project_location): # This function will read the .bos (Blender-Organizer Story) files. Primarily # to convert them to .vcss (VCStudio Story) files. # The concept is similar. But in order to work with them outside of reading # and writting the file directly. I want to make a dictionary format with in # the python. (Similarly to how I did with checklists and analytics) # In the .bos file there was what I thought was clever at the time but # ultimatly a dumb idea to make EVENTS and not scenes. The idea was that in # a single event could be multiple scenes. But in reality it very rearly used # and creating scenes with in events created a potential user error situation. # So I want to get rid of all the EVENTS and read Scenes directly. Unless # the event is empty of scenes. In which case to create a scene with the # text of that event. data = { "fraction": 0.0, # Percentage of the Scenes finished. "camera" : [0,0], # The position of where the user left "selected": [], # List of selected items in the story editor "active": None, # Active item. "scenes" : {}, # List of scenes. "arrows" : [], # List of connections. (A LIST. NOT DICT.) "links" : [], # List of links to files or assets. "markers": {}, # List of markers. "events" : {} # List of frame like containers. (It will have similar) } # function as events. But will have no text data with in # it. It will be more like Blender's node editor's Frame. # Even tho I want to change radically the idea of events. We still need to # think in the events terms. Because we are parsing and old file. Funny how # I will need to write this thing twice. For both file-types. # I can't read it the way I read most other files. Because it's not line- # based. But more something like HTML file. So it's going to be a little # issue. bos = open(project_location+"/pln/main.bos") bos = bos.read() cx, cy = 1, 1 if "" in bos: camera = bos[bos.find("")+8:] camera = camera[:camera.find("")] camera = camera.split(",") for num, val in enumerate(camera): try: camera[num] = float(val) except: camera[num] = 0.0 try: cx = float(camera[2]) cy = float(camera[3]) except: pass print (cy, cx) # Some stupid me made decision early in a story editor's life to use # per-pixel X coordinates and per line Y coordinates. Which is something # like 100 times the difference. Okay 50 ish is a default value for Y. # I'm not going to do anything about it right now. Maximum a bit later. camera = [camera[0], camera[1]] # I don't want scale to exist here. data["camera"] = camera # Events. They are very important. Now in the Blender-Organizer I used an # HTML like format to mark shots and assets with in the text. But it's not # a very efficient way of doing it. Because it requiered constant parsing # of the text in real time. Which caused quite a noticable lag. And also # every time I needed to read a part of it. I had to do parsing of the text. # So I guess the VCSS file format will be designed to deal with this kind of # thing. I'm actually more toward putting the text into separate files. Simple # text documents. And making the VCSS a linking system. if "" in bos: for event in bos.split("")[:-1]: event = event[event.find("")+8:] # Now we have text of the event. Let's parse out of it folowing # stuff. We will need it if there are multiple scenes. Or when # there are no scenes at all. eventname = event[event.find('"')+1:event.replace('"'," ",1).find('"')] # Let's parse the coordinates. c = event[event.find('[')+1:event.find(']')] c = c.split(",") eventpositon = [float(c[0])*cx,float(c[2])*cy] eventsize = [float(c[1])*cx,60.0] # Now since we know the name of the event and the sizes. We can # start parsing the scenes from the text with in the event. eventtext = event[event.find(']')+2:-1] # Now basically have to do the same exact thing with and # later with , to make all work. # But first let's record a scene if it has no scene in it. if not "" in eventtext: if eventname in data["scenes"]: eventname = eventname + "_copy" data["scenes"][eventname] = { "fraction":0.0, # Percentage "position":eventpositon, "size":eventsize, "parent":"", # For when it's in a Frame (Event) "shots":[[ "text_block",[["text", eventtext]] ]] } else: # If there are more then 1 scene per event. We want to create # an event, frame thing for them. aos = eventtext.count("") parent = "" #This will be it's name if aos > 1: parent = eventname data["events"][eventname] = { "position":eventpositon, "size":[0,0] } # Now let's continue parsing the scenes. for num, scene in enumerate(eventtext.split("")[:-1]): scenename = scene[scene.find('"')+1:scene.replace('"'," ",1).find('"')] scenename = scenename.replace(" ", "_") scenetext = scene[scene.replace('"', " ", 1).find('"')+1:-1] scenesize = [eventsize[0] / aos , eventsize[1]] sceneposition = [eventpositon[0] + scenesize[0]*num, eventpositon[1]] data["scenes"][scenename] = { "fraction":0.0, # Percentage "position":sceneposition, "size":scenesize, "parent":parent, # For when it's in a Frame (Event) "shots":[[ "text_block",[["text", scenetext]] ]] } # Parsing the text inside the scenes... OMG. Finding the SHOTS. for scenename in data["scenes"]: scenetext = data["scenes"][scenename]["shots"][0][1][0][1] # Okay so we have both scene name and scene text. So it's # time so send this data into the main data thingy, right? # Wrong. Now we need to parse pointers to the shots, assets # and other stuff. This file format gonna take a while. # If you look into the history of this file on NotABug there # Will be a version with a novel-long article here in the # comments. I was toying with ideas of how to make stuff # work. I will not change the whole system a bit from what # I though there. (Using text pointers) and make it more like # list of shot_block or text_block. Which both are just text. # But one has a bit more metadata then the other. And inside # which you can specify links, frases, images and simple text... # So first of all we need to break the text up into shots. shots = [] sa = scenetext.count("") # Shots amount ts = scenetext # Copy of the scene text to butcher for s in range(sa): # If the first part of the scene is not a shot. if not ts.startswith(""): shots.append([ "text_block", [["text",ts[:ts.find("")]]] ]) # Now we need to erase the part from the ts. ts = ts[ts.find("")+6:] # Now we can parse the shot itself. We need the name. if ts.count('"') > 1: shotname = ts[ts.find('"')+1:ts.replace('"', " ", 1).find('"')] ts = ts[ts.replace('"', " ", 1).find('"')+1:] else: shotname = "Unnamed" # Put it also into the list. shots.append([ "shot_block", shotname, [["text",ts[:ts.find("")]]] ]) # And erase it. ts = ts[ts.find("")+7:] shots.append([ "text_block", [["text",ts]] ]) # Now I want to get a fraction from a scene. shotsfractions = [] for shot in shots: if shot[0] == "shot_block": # Let's see if it has a checklist. if os.path.exists(project_location\ +"/rnd/"+scenename+"/"+shot[1]+"/shot.progress"): check = checklist.get_list(project_location\ +"/rnd/"+scenename+"/"+shot[1]+"/shot.progress") shotsfractions.append(check["fraction"]) else: folder = project_location\ +"/rnd/"+scenename+"/"+shot[1] try: if len(os.listdir(folder+"/rendered")) > 0: shotsfractions.append(1.0) elif len(os.listdir(folder+"/test_rnd")) > 0: shotsfractions.append(0.8) elif len(os.listdir(folder+"/opengl")) > 0: shotsfractions.append(0.6) elif len(os.listdir(folder+"/storyboard")) > 0: shotsfractions.append(0.4) elif len(os.listdir(folder+"/extra")) > 0: shotsfractions.append(0.2) except: shotsfractions.append(0.0) # Writting parsed verions of the scenes. With all the shots data["scenes"][scenename]["shots"] = shots # Now that we have SHOTS Fractions we need a scene fraction. try: data["scenes"][scenename]["fraction"] = \ sum(shotsfractions) / len(shotsfractions) except: data["scenes"][scenename]["fraction"] = 0.0 # Now since we have shots and we have their factors. I'd like to do # parsing of the text parts inside shots and texts between shots to # find all the images, links and phrases. for scenename in data["scenes"]: for shotnum, shot in enumerate(data["scenes"][scenename]["shots"]): # Our shot data could be either text_block ot shot_block # and they are formatted a little bit differently. if shot[0] == "shot_block": text = shot[2][0][1] else: text = shot[1][0][1] # Now keep in mind that similar stuff has to be done again # when we saving the parsed text back textblock = [] # This requires some extreme cleverness. Since I already hate # this file format. WHY IS IT SO COMPLICATED?! part = "" skip = 0 for num, letter in enumerate(text): if num >= skip: if part.endswith(""): # putting the normal text in if part[skip:].replace("", ""): textblock.append([ "text", part[skip:].replace("", "") ]) # parsing the item itempart = text[num:text[num:].find("")+num] link = itempart[itempart.find('"')+1: itempart.replace('"', " ", 1).find('"')] if link.startswith("/dev"): link = link[4:] itemtext = itempart[itempart.replace('"', " ", 1).find('"')+1:] # puttin the link into the list textblock.append([ "link", link, itemtext ]) # skiping to after the item skip = text[num:].find("")+6 part = "" # Images. With the <> elif part.endswith(""): # putting the normal text in if part[skip:].replace("", ""): textblock.append([ "text", part[skip:].replace("", "") ]) # parsing the item link = text[num:text[num:].find("")+num] textblock.append([ "image", link ]) # skiping to after the item skip = text[num:].find("")+7 part = "" # Images. With the [] (a little older version but still # should be supported by default) elif part.endswith("[image]"): # putting the normal text in if part[skip:].replace("[image]", ""): textblock.append([ "text", part[skip:].replace("[image]", "") ]) # parsing the item link = text[num:text[num:].find("[/image]")+num] textblock.append([ "image", link ]) # skiping to after the item skip = text[num:].find("[/image]")+7 part = "" # Now let's figure out the frases. Because it has a # text based thing in the beginning. Or maybe it's going # to be anything that was before it? Hmm... elif part.endswith(" - ["): # putting the normal text in if part[skip:].replace(" - [", ""): textblock.append([ "text", part[skip:].replace(" - [", "") ]) # I designed the - [ thing to type conversation # easier. Example: # John - [Hello, World.] # This would be John saying the words "Hello, World." # but with the inclusion of it became quite a # problem. Because I would like John to be a link. # It ended up looking something like: #"/dev/chr/John"John - [Hello, World.] # This is a bit of a problem. Because any link that # marked as is already in a textblock list. character = [] # If it's a link if textblock[-1][0] == "link": character = textblock[-1] del textblock[-1] # If it's just a text elif textblock[-1][0] == "text": character = textblock[-1][1] character = character[character.rfind("\n")+1:] if character: textblock[-1][1] = textblock[-1][1].replace(character, "") character = ["text", character] # Now let's get the frase frase = text[num:text[num:].find("]")+num] # If any character. Put it into a textblock if character: textblock.append([ "frase", character, frase ]) # skiping to after the item skip = text[num:].find("]") part = "" else: part = part + letter # LAST PART (WHY DIDN'T I THINK ABOUT ERLIER OMG) textblock.append([ "text", part[skip:] ]) if shot[0] == "shot_block": data["scenes"][scenename]["shots"][shotnum][2] = textblock else: data["scenes"][scenename]["shots"][shotnum][1] = textblock # Okay this was event. LOL. This is a large function to parse them all. # Crazy complex bastards. Anyway. It's over. And we have to only do the # easy rest of it. Untill we will get to calculating the project. Because # we need to filter out all scenes that are not in the main chain. # Let's start with hard thing first. So to feel good when having to do the # easy stuff in the end. ARROWS. (connections between scenes) # To make it work. We need to fisrt of all earase all the stuff before # the first arrow. Because could be mentioned in the script it # self. Similarly is too. So it's good to complitelly clear bos # out of anything that came before. bos = bos[bos.rfind(""):] # From here everything is PER LINE BASED... YEAH!!!! bos = bos.split("\n") for line in bos: # This is arrows... if line.startswith(""): arrow = line[line.find("<")+7:line.rfind(" ") newarrow = [] for i in arrow: i = i.split(",") if i[0] != "-1": newarrow.append([ "scene", i[1][1:-1] ]) else: newarrow.append( i[1][1:-1] ) data["arrows"].append(newarrow) # And this is links. Formerly known as Images. Initially the idea was to # just store images. But later. I started adding assets like this as well. elif line.startswith(""): stuff = line.split(",") link = stuff[3][1:-1] coordinates = [] try: coordinates.append(float(stuff[0].replace("", ""))*cx) except: coordinates.append(0.0) try: coordinates.append(float(stuff[1])*cy) except: coordinates.append(0.0) # Lately the primary reason to use was linking assets directly # into story editor's space. But it was a hack. Basically I was # linking a preview image. Preview.png or Preview.jpg from the # renders of the asset. And the drawer of the images in the story- # editor already knew to redirect the clicks to a different function. # But for VCStudio I want to link to asset or a file. linktype = "file" if "/renders/Preview." in link: linktype = "asset" link = link[:link.rfind("/renders/Preview.")].replace("/dev", "") data["links"].append([ linktype, link, coordinates, "" ]) # Markers. I nearly forgot about the markers. In the Story-Editor of # Blender-Orgaznier they were something like markers in VSE in Blender. # Vertical lines visible on every altitude. elif line.startswith(""): marker = line.replace("", "").replace("", "").split(",") try: markloc = float(marker[0]) except: markloc = 0.0 markstring = marker[1].replace('"', "") # I do want to do something quite interesting with markers in the # VCStudio tho. I want them to be like little nodes with text while # in the frame. And be like directional guides. Sticking to the # edges of the screen when outside the screen. Something like items # that are outside of the map in the videogames. So you can see # which direction are they. For this markers will need to have # both X and Y coordinates. data["markers"][markstring] = [markloc, 0.0, ""] # For now the Y is 0. Because we are reading from the old version. # Okay we've got the Arrows data. Pretty much all the data. Now we just # need to calculate the scenes fraction. For that we need to recreate the # train of scenes. From START till END. fractions = [] lastarrow = 'start' for i in data["arrows"]: if lastarrow == "end": break for arrow in data["arrows"]: if arrow[0] == lastarrow: lastarrow = arrow[1] if arrow[1] != "end": fractions.append( data["scenes"][arrow[1][1]]["fraction"] ) else: break # FINAL STUFF... try: data["fraction"] = sum(fractions) / len(fractions) except: data["fraction"] = 0.0 #save(project_location, data) #data = load(project_location) return data def get_asset_data(win, name, force=False): # This function will return a data of about the asset. if name not in win.assets or force: data = { "fraction":0.0 } try: # Now let's get a fraction of the asset if os.path.exists(win.project+"/ast/"+name+".blend"): data["fraction"] = 1.0 else: check = checklist.get_list(win.project+"/dev/"+name+"/asset.progress") data["fraction"] = check["fraction"] except: pass win.assets[name] = data return win.assets[name] def save(project, story): # This is a save of the new VCStudio. Which is using a standard file format. # so people could parse the data themselves. ( Or because I'm lazy ) try: os.mkdir(project+'/pln/') except: pass with open(project+'/pln/story.vcss', 'w') as fp: json.dump(story, fp, sort_keys=True, indent=4) def load(project): # This is a convertion back from JSON data to out beloved python dict try: with open(project+'/pln/story.vcss') as json_file: data = json.load(json_file) except: # If there is no file. data = { "fraction": 0.0, # Percentage of the Scenes finished. "camera" : [0,0], # The position of where the user left "selected": [], # List of selected items in the story editor "active": None, # Active item. "scenes" : {}, # List of scenes. "arrows" : [], # List of connections. (A LIST. NOT DICT.) "links" : [], # List of links to files or assets. "markers": {}, # List of markers. "events" : {} # List of frame like containers. (It will have similar) } # function as events. But will have no text data with in # it. It will be more like Blender's node editor's Frame. # I forgot to update the scenes analytics lol. for scenename in data["scenes"]: shotsfractions = [] shotslengths = [] textlength = 0 prevtextpos = 0 for shot in data["scenes"][scenename]["shots"]: for t in shot[-1]: textlength = textlength + len(t[-1]) if shot[0] == "shot_block": # Let's see if it has a checklist. if os.path.exists(project\ +"/rnd/"+scenename+"/"+shot[1]+"/shot.progress"): check = checklist.get_list(project\ +"/rnd/"+scenename+"/"+shot[1]+"/shot.progress") shotsfractions.append(check["fraction"]) else: folder = project\ +"/rnd/"+scenename+"/"+shot[1] try: if len(os.listdir(folder+"/rendered")) > 0: shotsfractions.append(1.0) elif len(os.listdir(folder+"/test_rnd")) > 0: shotsfractions.append(0.8) elif len(os.listdir(folder+"/opengl")) > 0: shotsfractions.append(0.6) elif len(os.listdir(folder+"/storyboard")) > 0: shotsfractions.append(0.4) elif len(os.listdir(folder+"/extra")) > 0: shotsfractions.append(0.2) except: shotsfractions.append(0.0) shotslengths.append( textlength - prevtextpos ) prevtextpos = textlength # Adjusting fractions based on the length of the text marked. lastlen = 0 for t in data["scenes"][scenename]["shots"][-1][-1]: lastlen = lastlen + len(t[-1]) try: avgtxtlng = sum(shotslengths) / len(shotslengths) except: avgtxtlng = 1 avgtxtlng = min(1, avgtxtlng) sf = [] for n, i in enumerate(shotsfractions): try: l = shotslengths[n] except: l = 0 nf = l / (textlength - lastlen) * i sf.append(nf) # If the last block isn't shot_block. It's safe to assume that # not all of the shots are yet marked in the text. Therefor we # want to estimate how much is marked. multiply_by = 1 if not data["scenes"][scenename].get("no_more_shots") and data["scenes"][scenename]["shots"][-1][0] == "text_block": try: multiply_by = (textlength - lastlen) / textlength except: multiply_by = 1 try: data["scenes"][scenename]["fraction"] = \ ( sum(sf) ) * multiply_by except: data["scenes"][scenename]["fraction"] = 0.0 fractions = [] lastarrow = 'start' for i in data["arrows"]: if lastarrow == "end": break for arrow in data["arrows"]: if arrow[0] == lastarrow: lastarrow = arrow[1] if arrow[1] != "end": fractions.append( data["scenes"][arrow[1][1]]["fraction"] ) else: break # FINAL STUFF... try: data["fraction"] = sum(fractions) / len(fractions) except: data["fraction"] = 0.0 return data def undo_record(win): # This function will record undo of the story. The undo will be basically # copies of the script JSON file as a whole. The /pln/story.vcss # This function will not read the files. It's not nessesary. But it will # make a copy of the whole thing. So I guess we need to implement some kind # of limiter. try: limit = int(win.settings["Undo_Limit"]) # Now let's advance the undo history 1 forward. win.undo_index = min(len(win.undo_history)+1, limit) # Let's read our file. Which might not exist. f = open(win.project+'/pln/story.vcss') win.undo_history.append(f.read()) # And now let's limit our selves win.undo_history = win.undo_history[0-limit:] except: pass # Following 2 functions are a big buggy at the moment. I will aprecieate if # someone going to look at them. Maybe it's to do with studio/studio_storyLayer.py # where I was testing them. But they work. Just not every time. def undo(win): # This function will do the undo. Tho this function will not nessesarily # sens the Ctrl - Z by it self. It should be activated from any place # in the UI. # Let's move our index back 1 way. win.undo_index = max(0, win.undo_index-1) # Now let's read our data d = win.undo_history[win.undo_index] # And let's save the data back to the file f = open(win.project+'/pln/story.vcss', "w") f.write(d) f.close() # And let's refresh story win.story = load(win.project) def redo(win): # This function will do the redo. Tho this function will not nessesarily # sens the Ctrl - Y by it self. It should be activated from any place # in the UI. # Let's move our index back 1 way. win.undo_index = min(len(win.undo_history)-1, win.undo_index+1) # Now let's read our data d = win.undo_history[win.undo_index] # And let's save the data back to the file f = open(win.project+'/pln/story.vcss', "w") f.write(d) f.close() # And let's refresh story win.story = load(win.project) def export_to_odt(win): # This function will export the story to the ODT format. The ODT format is # basically a zip file. With images and other stuff. And a context.xml file # where you have all the styles and the text with task of style. This is # very handy since we need to only make a simple text document with a folder # of images. # First let's call a file save dialog. I'm going to use the standard Gtk # one. Maybe will make my own. folderchooser = Gtk.FileChooserDialog(talk.text("export_tooltip"), None, Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE, Gtk.ResponseType.OK)) folderchooser.set_default_response(Gtk.ResponseType.OK) response = folderchooser.run() if response == Gtk.ResponseType.OK: savefilename = folderchooser.get_filename() else: folderchooser.destroy() return folderchooser.destroy() # We are going to start the folder for this converion. For this in the # new_file directory we have an example ODT file. It's basically a zip # file that we want to use. Let's unzip it. referencefile = "/new_file/reference.odt" # Let's clear the folder from previouw parsing if such exists. try: os.system("rm -rf /tmp/odt_export") except: pass # And make the folder again. os.makedirs("/tmp/odt_export") # Now let's unzip our contents into there. ref = zipfile.ZipFile(os.getcwd()+referencefile, "r") ref.extractall("/tmp/odt_export") ref.close() # Now there will be the Pictures directory that contains the test image # of Moria standing in front of the window and looking at the racetrack. # we don't need it. But we need the folder it self. try: os.remove("/tmp/odt_export/Pictures/1000020100000780000004380D1DEC99F7A147C7.png") except: pass # By the way. If you want to look at this image. Just into the reference.odt # you will see it there in the text. # Now we going to start writing to the context.xml file. So let's open it. save = open("/tmp/odt_export/content.xml", "w") # First stuff that we are going to write are the headers. So LibreOffice # will not start going nutz. And saying how corrupter the file is. save.write('''''') # Now let's write the name of the project save.write(''+win.analytics["name"]+'') # Now let's write the director's name save.write(''+win.analytics["director"]+'') # Now let's write the comment save.write(''+win.analytics["status"]+'') # Now let's write scenes them selves. We don't want all scene but only # ones in the main chain. So we are ignoring the scenes that are meant for # tests and such. lastarrow = 'start' for i in win.story["arrows"]: if lastarrow == "end": break for arrow in win.story["arrows"]: if arrow[0] == lastarrow: lastarrow = arrow[1] if arrow[1] != "end": # Here is our scene name. save.write(''+arrow[1][1]+'') save.write('') # We need to get all the assets of the scene and write them # net under the scene name. assets = get_assets(win, arrow[1][1]) for cur in ["chr", "veh", "loc", "obj"]: t = talk.text(cur)+": " for a in assets: if cur in a: t = t +a[a.rfind("/")+1:]+", " t = t[:-2] if t != talk.text(cur): save.write(''+t+'') # Now let's actually write the text of the scene in there. scene = win.story["scenes"][arrow[1][1]]["shots"] save.write('') save.write('') for block in scene: # For just text parts. if block[0] == "text_block": style = "T3" else: # If It's a shot. I want the shot color marking shotis = block[1] rcolors = [ "shot_1", "shot_2", "shot_3", "shot_4", "shot_5" ] # Getting the color. It's not always works. if "shot_colors" not in win.story: win.story["shot_colors"] = {} surl = "/"+arrow[1][1]+"/"+shotis if surl not in win.story["shot_colors"]: win.story["shot_colors"][surl] = rcolors[len(win.story["shot_colors"]) % len(rcolors)] style = win.story["shot_colors"][surl] # Now let's itterate over the text in the block. for text in block[-1]: # For just regular parts we have our regular text if text[0] == "text" or text[0] == "link": for line in re.split("(\n)",text[-1]): if line == "\n": save.write('') save.write('') else: save.write(''+line+'') # For frases it's a bit different elif text[0] == "frase": # We gonna close the simle text part. save.write('') # We gonna make a new line save.write('') # We gonna write the name of the speaker save.write(''+text[1][-1]+'') # Then we open the text type save.write('') # Then we write the frase for line in re.split("(\n)",text[-1]): if line == "\n": save.write('') save.write('') else: save.write(''+line+'') # Then we close the frase save.write('') # And open back the other one save.write('') # Now let's make images work. Okay. So there is our # images folder. We need to copy our image to there # first. elif text[0] == "image": # The image could be relative or absolute. tmpurl = text[-1].replace("/", "_") if os.path.exists(win.project+text[-1]): f = open(win.project+text[-1], "rb") else: f = open(text[-1], "rb") s = open("/tmp/odt_export/Pictures/"+tmpurl, "wb") s.write(f.read()) f.close() s.close() # Now we have a copy of the image in the # file. We need to draw it. save.write('') save.write('') save.write('') save.write('') save.write('') # There is a problem tho. We need to write # the existance of this file into another # place. if we don't do that. LibreOffice will # think that the ODT is broken. So it will fix # it and all gonna be great. Still not the best # experience for the users. Please somebody # figure it out. save.write('') # After every scene is want a little line in the bottom save.write('') save.write('') save.write('') else: break # Now let's close the file save.write('') save.close() # And now let's make the odt file in the given savefilename place def zip_odt(src, dst): zf = zipfile.ZipFile("%s.odt" % (dst), "w", zipfile.ZIP_DEFLATED) #changed file extension abs_src = os.path.abspath(src) for dirname, subdirs, files in os.walk(src): for filename in files: absname = os.path.abspath(os.path.join(dirname, filename)) arcname = absname[len(abs_src) + 1:] zf.write(absname, arcname) zf.close() zip_odt("/tmp/odt_export", savefilename) def get_assets(win, scene): # This funtion will get the assets from any given scene. assets = [] # Now let's read through the scene. for shot in win.story["scenes"][scene]["shots"]: # Let's read the shot for asset in shot[-1]: # Ignore text if asset[0] == "text": continue # It's it's a staight up link. Just add it. if asset[0] == "link" and asset[1] not in assets: assets.append(asset[1]) # It's a link in a frase. if asset[0] == "frase" and asset[1][0] == "link" and asset[1][1] not in assets: assets.append(asset[1][1]) return assets