#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ ~LCD Game Shrinker~ main script. This program permits to shrink MAME high resolution artwork and graphics for protable device running LCD game emulator. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ __author__ = "bzhxx" __contact__ = "https://github.com/bzhxx" __license__ = "GPLv3" import sys, os, subprocess from pathlib import Path import re, lxml from struct import pack from PIL import Image,ImageChops import importlib import svgutils import zipfile import zlib import lz4.frame as lz4 from pyunpack import Archive import numpy as np #ROM default defintion & parser import rom_config as rom import rom_parser artwork_rpath="../artwork/" output_dir ="./output/" DEBUG = False # select if the ROM data are compressed with ZLIB, LZ4 or LZMA LZ4_COMPRESSOR =1 ZLIB_COMPRESSOR=2 LZMA_COMPRESSOR=3 COMPRESS_WITH = LZ4_COMPRESSOR #COMPRESS_WITH = LZMA_COMPRESSOR #G&W LCD resolution gw_width=320 gw_height=240 def log(s): if DEBUG: print (s) def warm(s): print ( " WARNING:"+s +' ['+str(rom_name) +']') def error(s): print (" ERROR:"+s +' [' +str(rom_name) +']') exit() #try to locate tools inkscape_path = os.environ["INKSCAPE_PATH"] if "INKSCAPE_PATH" in os.environ else "inkscape" # Print iterations progress def printProgressBar (iteration, total, prefix = '', suffix = '', length = 20, fill = "▆"): """ Call in a loop to create terminal progress bar @params: iteration - Required : current iteration (Int) total - Required : total iterations (Int) prefix - Optional : prefix string (Str) suffix - Optional : suffix string (Str) length - Optional : character length of bar (Int) fill - Optional : bar fill character (Str) """ filledLength = int(length * iteration // total) bar = fill * filledLength + '-' * (length - filledLength) if not DEBUG: sys.stdout.write('\r'+prefix+'|'+bar+'| '+str(iteration)+"/"+str(total)+' '+suffix) sys.stdout.flush() def osmkdir (path): if not os.path.isdir(path): os.mkdir(path) if len(sys.argv) < 2: rom_file = "./input/rom/" else: rom_file = sys.argv[1] rom_path = os.path.dirname(rom_file) rom_name = os.path.splitext(os.path.basename(rom_file))[0] #checks args :if path is a directory process all files if os.path.isdir(rom_file) : ## get all files with .zip extension ## call this script for each file for x in os.listdir(rom_file) : if x.endswith(".zip") or x.endswith(".7z"): subprocess.run([sys.executable, sys.argv[0], os.path.join(rom_path, str(x))]) exit() printProgressBar(0, 1, prefix = rom_name.ljust(25), suffix = 'Unzip') artwork_path=os.path.join(rom_path,artwork_rpath) artwork_file=os.path.join(artwork_path,rom_name+".zip") # Create directories to build things built_root = os.path.join('.',"build") preview_root = os.path.join('.',"preview") title_image_root = os.path.join('.',"title") rom.build_dir =os.path.join(built_root,rom_name) rom.mame_rom_dir = os.path.join(rom.build_dir ,"original") osmkdir(built_root) osmkdir(preview_root) osmkdir(title_image_root) osmkdir(rom.build_dir) osmkdir(rom.mame_rom_dir) # Output dir : create it for generated ROM file(s) osmkdir(output_dir) #unzip original rom if rom_file.endswith(".zip"): with zipfile.ZipFile(rom_file, 'r') as zip_ref: zip_ref.extractall(rom.mame_rom_dir) elif rom_file.endswith(".7z"): Archive(rom_file).extractall(rom.mame_rom_dir) else: raise ValueError(f"Unknown extension for rom {rom_file}") #unzip Artwork if os.path.isfile(artwork_file) : with zipfile.ZipFile(artwork_file, 'r') as zip_ref: zip_ref.extractall(rom.mame_rom_dir) else: error('No Artwork ? '+str(artwork_file)) rom_parser.set_parameters(rom_name, rom.mame_rom_dir) if not rom.found : printProgressBar(1, 1, prefix = rom_name.ljust(25), suffix = 'Failed ( unknown )') print("") exit() #need some files from a rom parent ? parent_file=rom_path+'/'+rom.mame_parent.strip()+'.zip' if os.path.isfile(parent_file) : log('parent found:'+parent_file) with zipfile.ZipFile(parent_file, 'r') as zip_ref: zip_ref.extractall(rom.mame_rom_dir) rom_parser.set_parameters(rom_name, rom.mame_rom_dir) parent_file=rom_path+'/'+rom.mame_parent.strip()+'.7z' if os.path.isfile(parent_file) : log('parent found:'+parent_file) Archive(parent_file).extractall(rom.mame_rom_dir) rom_parser.set_parameters(rom_name, rom.mame_rom_dir) if rom.mame_fullname == "'unknown game'": error("There is no entry for this game") log(str(rom.mame_fullname)) if rom.dual_screen_vert or rom.dual_screen_hor: error('Dual screen not supported') background_file = os.path.join(rom.mame_rom_dir,rom.background_file) segments_file = os.path.join(rom.mame_rom_dir,rom.segments_file) program_file = os.path.join(rom.mame_rom_dir,rom.program_file) melody_file = os.path.join(rom.mame_rom_dir,rom.melody_file) ### output files #svg files seg_file = os.path.join(rom.build_dir,"segments.svg") seg_shadow_file = os.path.join(rom.build_dir,"segments_shadow.svg") MAME_seg_file = os.path.join(rom.build_dir,"MAME_segments.svg") #background png files back_file = os.path.join(rom.build_dir,"background.png") MAME_back_file = os.path.join(rom.build_dir,"MAME_background.png") jpeg_background = os.path.join(rom.build_dir,"gnw_background.jpg") # intermediate ROM file (raw / uncompressed) # without JPEG background # with or without 565 16bits background rom_filename = os.path.join(rom.build_dir, rom_name+".gw") # Final ROM file (compressed) final_rom_filename = os.path.join(output_dir,str(rom.mame_fullname+".gw")) ### Files to build BGD_FILE = os.path.join(rom.build_dir,"gnw_background") SGD_FILE = os.path.join(rom.build_dir,"gnw_segments") SGD_FILE_4BITS = os.path.join(rom.build_dir, "gnw_segments_4bits") SGD_FILE_2BITS = os.path.join(rom.build_dir, "gnw_segments_2bits") SGO_FILE = os.path.join(rom.build_dir,"gnw_segments_offset") SGX_FILE = os.path.join(rom.build_dir,"gnw_segments_x") SGY_FILE = os.path.join(rom.build_dir,"gnw_segments_y") SGH_FILE = os.path.join(rom.build_dir,"gnw_segments_height") SGW_FILE= os.path.join(rom.build_dir,"gnw_segments_width") MLD_FILE = melody_file PGM_FILE= program_file BTN_FILE = os.path.join(rom.build_dir,"gnw_buttons") ### Adapt Artwork background to target ###################################################################################### bar_prefix=(rom.mame_fullname).split(": ",1)[-1].split(" (",1)[0] if rom.custom_script_notfound : bar_prefix=bar_prefix+'*' bar_prefix=bar_prefix.ljust(25) printProgressBar(1, 3, prefix = bar_prefix, suffix = 'Shrink artwork ') # check if it exists background_file =os.path.join(rom.mame_rom_dir, rom.background_file) if os.path.isfile(background_file) : #Remove alpha layer png = Image.open(background_file).convert('RGBA') img_background = Image.new("RGBA", png.size, (255, 255, 255)) alpha_composite = Image.alpha_composite(img_background, png).convert('RGB') #resize to MAME MAME_composite = alpha_composite.resize((rom.background_width,rom.background_height),Image.LANCZOS) MAME_composite.save(MAME_back_file) # resize to target keeping aspect ratio or full screen if rom.keep_aspect_ratio: scale_x = float(gw_width) / float(rom.background_width) scale_y = float(gw_height) / float(rom.background_height) # select the factor and position x,y if scale_x < scale_y: bgnd_width = gw_width bgnd_height= int(scale_x * float(rom.background_height)) mov_x = 0 mov_y = int((gw_height - bgnd_height)/2) else: bgnd_width = int(scale_y * float(rom.background_width)) bgnd_height= gw_height mov_y = 0 mov_x = int((gw_width - bgnd_width)/2) alpha_composite = alpha_composite.resize((bgnd_width,bgnd_height),Image.LANCZOS) img_jpeg_background=alpha_composite.copy() # Create background data section in RGB565 tmp_new_background = Image.new(mode="RGB", size=(gw_width, gw_height),color=(0,0,0)) tmp_new_background.paste(alpha_composite, (mov_x, mov_y)) tmp_new_background.save(back_file) alpha_composite = Image.open(back_file).convert('RGB') else: alpha_composite = alpha_composite.resize((gw_width,gw_height),Image.LANCZOS) alpha_composite.save(back_file) img_jpeg_background=alpha_composite.copy() ## Create JPEG background ### ###################################################################################### ## remove black borders to reduce the file size ## #get black pixels boolean if rom.crop_jpeg_background_border : image_mask = np.array(img_jpeg_background) != (0.,0.,0.) image_mask=image_mask[:,:,1] width, height = img_jpeg_background.size mask0,mask1=image_mask.any(0),image_mask.any(1) x0,x1=mask0.argmax(),width-mask0[::-1].argmax() y0,y1=mask1.argmax(),height-mask1[::-1].argmax() # crop right and bottom borders bbox=(x0,y0,x1,y1) img_jpeg_background = img_jpeg_background.crop(bbox) # Create background data section in JPEG img_jpeg_background.save(jpeg_background, optimize=True, quality=rom.jpeg_quality ) # Create background data section in 16bits reduced space color pixels = list(alpha_composite.getdata()) ## Create RGB background ### ###################################################################################### with open(BGD_FILE, 'wb') as f: for pix in pixels: # round MSB according to LSB value r_pix = rom.background_resolution*8*int(round(float(pix[0]) / (rom.background_resolution*8.0))) g_pix = rom.background_resolution*4*int(round(float(pix[1]) / (rom.background_resolution*4.0))) b_pix = rom.background_resolution*8*int(round(float(pix[2]) / (rom.background_resolution*8.0))) # check saturation if r_pix > 255 : r_pix=255 if g_pix > 255 : g_pix=255 if b_pix > 255 : b_pix=255 # RGB888 to RGB565 : drop LSB r = (r_pix >> 3) & 0x1F g = (g_pix >> 2) & 0x3F b = (b_pix >> 3) & 0x1F f.write(pack('H', (r << 11) + (g << 5) + b)) else: # warm user warm('No background found') if not os.path.isfile(segments_file): #Error there is no .svg file error('No segment file ('+segments_file+')') if not os.path.isfile(program_file): #Error, there is no program file error('No program_file ('+program_file+')') ### Adapt segments file to target printProgressBar(1, 3, prefix = bar_prefix, suffix = 'Load segments ') #Get segments from original segments file svg = svgutils.transform.fromfile(segments_file) originalSVG = svgutils.compose.SVG(segments_file) tree = lxml.etree.parse(segments_file, parser=lxml.etree.XMLParser(huge_tree=True)) svg_root = tree.getroot() viewbox = re.split('[ ,\t]+', svg_root.get('viewBox', '').strip()) viewbox_width=float(viewbox[2]) viewbox_height=float(viewbox[3]) log("View box width:"+str(float(viewbox_width))) log("View box height:"+str(float(viewbox_height))) ### Adapt Original graphic files to target screen size # Rescale and Align segments (MAME scale) originalSVG.scale(rom.screen_width/viewbox_width,rom.screen_height/viewbox_height) originalSVG.moveto(rom.screen_x-rom.background_x,rom.screen_y-rom.background_y) # Save to SVG file as an intermediate result MAME_figure = svgutils.compose.Figure(rom.background_width,rom.background_height, originalSVG) MAME_figure.save(MAME_seg_file) ## Rescale the Segments file to LCD Game&Watch scale #Get segments from original segments file ### Adapt segments file to target printProgressBar(2, 3, prefix = bar_prefix, suffix = 'Shrink segments', length = 20) svg = svgutils.transform.fromfile(MAME_seg_file) tree = lxml.etree.parse(MAME_seg_file, parser=lxml.etree.XMLParser(huge_tree=True)) svg_root = tree.getroot() viewbox = re.split('[ ,\t]+', svg_root.get('viewBox', '').strip()) viewbox_width = float(viewbox[2]) viewbox_height = float(viewbox[3]) # keep aspect ratio or full screen originalSVG = svgutils.compose.SVG(MAME_seg_file) if rom.keep_aspect_ratio: scale_x = float(gw_width) / viewbox_width scale_y = float(gw_height) / viewbox_height # select the factor and postion x,y if scale_x < scale_y: scale = scale_x mov_x = 0 mov_y = (gw_height - (scale * viewbox_height))/2 else: scale = scale_y mov_x = (gw_width - (scale * viewbox_width))/2 mov_y = 0 # reshape the figure originalSVG.scale(scale, scale).move(mov_x, mov_y) else: originalSVG.scale(gw_width/viewbox_width,gw_height/viewbox_height) figure = svgutils.compose.Figure(gw_width,gw_height, originalSVG) figure.save(seg_file) ### Generate drop shadow LCD segments file (experimental) # need to improve this and to add user paremeters for dx,dy,stDeviation and flood-opacity. # filter definition for drop shadow effect filter_def = """ """ filter2_def = """ """ # TODO: need to improve this to cover more cases search_string ="fill:#000000;fill-opacity:0.8" nofilter_string ="fill:#000000;fill-opacity:0.84705883" nofilter_string2="fill:#000000;fill-opacity:0.84705882" filter_string = "fill:#000000;fill-opacity:0.84705883;filter:url(#filter_drop_shadow)" svg_found = False filter_notinjected = True with open(seg_file,"r") as input_file : with open(seg_shadow_file,"w") as output_file: while True: content=input_file.readline() if not content: break if '' in content and svg_found and filter_notinjected: output_file.write(filter_def) filter_notinjected = False if search_string in content: content=content.replace(nofilter_string,filter_string) content=content.replace(nofilter_string2,filter_string) output_file.write(content) else: output_file.write(content) ################################################################################################### ### Create a preview of the final rendering ################################################################################################### #Change the svg source file if drop_shadow filter feature is enabled if (rom.drop_shadow ) and (not rom.flag_rendering_lcd_inverted): seg_preview_file = seg_shadow_file else: seg_preview_file = seg_file preview_file = os.path.join(preview_root,rom.mame_fullname.replace(":","")+".png") title_image_file = os.path.join(title_image_root,rom.mame_fullname.replace(":","")+".png") seg_png_file = os.path.join(rom.build_dir,rom.name+".png") if rom.flag_rendering_lcd_inverted: cmd = " "+seg_preview_file+" --export-type=png --export-overwrite"+ " --export-background=#000000 --export-background-opacity=1"+" --export-type=png"+" -o "+seg_png_file else: cmd = " "+seg_preview_file+" --export-type=png --export-overwrite"+ " --export-background=#FFFFFF --export-background-opacity=1"+" --export-type=png"+" -o "+seg_png_file cmd = inkscape_path+cmd inkscape_output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL,shell=True) log(inkscape_output) #mix background and segment to get a preview background_preview = Image.open(back_file).convert('RGB') segment_preview = Image.open(seg_png_file).convert('RGB').resize((background_preview.size)) preview = Image.new("RGB", (background_preview.size)) preview = ImageChops.multiply(background_preview, segment_preview) # copy the generated preview to make just the title image title_image = preview.copy().resize((128,96)) unit= Image.open("./custom/unit_x206y99.png").convert('RGB') unit.paste(preview,(206,99)) # save it unit.save(preview_file) if rom.rotate: unit = Image.open(preview_file).rotate(90, expand=True) unit.save(preview_file) # save title image title_image.save(title_image_file) ################################################################################################### ### open/save svg file using Inkscape (workaround to fix modification applied on path name by Inkscape) ################################################################################################### seg_file_fix =os.path.join(rom.build_dir,"segments_fix.svg") cmd = " "+ seg_file + " --query-all"+" --export-overwrite --export-type=svg" + " -o " + seg_file_fix cmd = inkscape_path + cmd log(cmd) inkscape_output=subprocess.check_output(cmd,stderr=subprocess.DEVNULL,shell=True) log(inkscape_output) seg_file = seg_file_fix ##### if (rom.drop_shadow ) and (not rom.flag_rendering_lcd_inverted): seg_shadow_file_fix =os.path.join(rom.build_dir,"segments_shadow_fix.svg") cmd = " "+ seg_shadow_file + " --query-all"+" --export-overwrite --export-type=svg" + " -o " + seg_shadow_file_fix cmd = inkscape_path + cmd log(cmd) inkscape_output=subprocess.check_output(cmd,stderr=subprocess.DEVNULL,shell=True) log(inkscape_output) seg_shadow_file = seg_shadow_file_fix ################################################################################################### ## parse all the objects in the svg file and keep only relevant ones ################################################################################################### # object dimensions index : returned by Inkscape query ID=0 X=1 Y=2 WIDTH=3 HEIGHT=4 # defines the maximum number of segments NB_SEGMENTS=256 # depending on the CPU, # the segment position is determined like this: # for SM510 series: output to x.y.z, where: # x = group a/b/bs/c (0/1/2/3) # y = segment 1-16 (0-15) # z = common H1-H4 (0-3) #segment position = 64*x + 4*y + z # For SM500 series: output to x.y.z, where: # x = O group (0-*) # y = O segment 1-4 (0-3) # z = common H1/H2 (0/1) #segment position = 8*x + 2*y + z # Init all tables tab_x = [0]*NB_SEGMENTS tab_y = [0]*NB_SEGMENTS tab_offset = [0]*NB_SEGMENTS tab_width = [0]*NB_SEGMENTS tab_height = [0]*NB_SEGMENTS tab_id = [0]*NB_SEGMENTS #clean up the segments data file(s) wr = open(SGD_FILE, 'w') wr.close() wr = open(SGD_FILE_4BITS, 'w') wr.close() wr = open(SGD_FILE_2BITS, 'w') wr.close() #Get all objects from svg file using Inkscape #it's important to extract x,y coordinates from 'no shadow' segment file #because injecting drop shadow effect change original coordinates objects_all = subprocess.check_output([inkscape_path, "--query-all",seg_file],stderr=subprocess.DEVNULL) log(objects_all) # get XML from .svg file svg = svgutils.transform.fromfile(seg_file) #Init progress bar bar_total=len(objects_all.splitlines()) bar_progress=0 ## parse all the objects in the svg file and keep only relevant ones for obj in objects_all.splitlines(): # Index of the object in svg file obj_id = obj.split(b',')[0].decode('utf-8') #keep only ID: obj_pos_start = 0 obj_title_found = False bar_progress=bar_progress+1 printProgressBar(bar_progress, bar_total, prefix = bar_prefix, suffix = 'Parse segments ') try: obj_xml = svg.find_id(obj_id) except: pass log("object not found:"+obj_id) else: obj_xmlstr = obj_xml.tostr() search_title = obj_xmlstr.find(b"id=\"title") nb_title = obj_xmlstr.count(b"id=\"title") # title found and only one object id if (search_title > -1) and (nb_title == 1) : obj_title_found = True log("OBJ>" + obj_id) #get the title x.y.z segment_xyz=re.findall(br">([^]]*)([^]]*) gw_width: #crop right side by x+width - gw_width image = image.crop((0, 0, gw_width -x ,height)) width, height = image.size if y+height > gw_height: #crop upper side by y+height - gw_height image = image.crop((0, 0, width, gw_height -y)) width, height = image.size ## negative x,y check if x < 0: # crop right side by x image = image.crop((-x, 0, width, height)) width, height = image.size x=0 if y < 0: # crop right side by y image = image.crop((0, -y, width, height)) width, height = image.size y=0 image.save(PNG_FILE,"PNG") image.close() #restore dims and coords tab_x[seg_pos]=x tab_y[seg_pos]=y tab_width[seg_pos]=width tab_height[seg_pos]=height tab_offset[seg_pos]=os.path.getsize(SGD_FILE) ###### Use PIL to get segments in 8bits #open and merge ARGB #keep 1 colour png = Image.open(PNG_FILE).convert('RGBA') img_seg = Image.new("RGBA", png.size, (255, 255, 255)) img_composite = Image.alpha_composite(img_seg, png).convert('RGB') with open(SGD_FILE, 'rb+') as file: #Go to the end of file file.seek(0,2) seg_value_8bits = bytearray((img_composite.getdata(band=1))) file.write(seg_value_8bits) ## Padding to 16 bits aligned in case of we need to consider 4 bits resolution rd_modulo = os.path.getsize(SGD_FILE) % 2 if rd_modulo != 0: with open(SGD_FILE, 'rb+') as file: file.seek(0,2) file.write(pack("c", b'P')) ##### Create 4 bits segments value resolution file #remove LSB and pack 2 pixels as 1 byte with open(SGD_FILE, 'rb') as in_file: segment_data_in = in_file.read() with open(SGD_FILE_4BITS, 'wb') as out_file: for msb,lsb in zip(segment_data_in[0::2],segment_data_in[1::2]): lsb = lsb >> 4 msb = msb >> 4 segment_data_out = lsb | (msb <<4) out_file.write(pack("=B",segment_data_out)) ##### Create 2 bits segments value resolution file #remove LSB and pack 4 pixels as 1 byte with open(SGD_FILE_2BITS, 'wb') as out_file: for lsb2,lsb,msb,msb2 in zip(segment_data_in[0::4],segment_data_in[1::4],segment_data_in[2::4],segment_data_in[3::4]): lsbr = lsb >> 6 msbr = msb >> 6 lsbr2 = lsb2 >> 6 msbr2 = msb2 >> 6 segment_data_out = msbr2 << 6 | msbr << 4 | lsbr << 2 | lsbr2 out_file.write(pack("=B",segment_data_out)) #### Create Segment Coordinates files out_filename = SGX_FILE with open(out_filename, "wb") as out_file: for c in tab_x: out_file.write(pack(" offset="+str(rom_offset)+" size="+str(elt_size)) #determine next offset (aligned 32bits) rom_offset+=elt_size if (rom_offset % 4) != 0: log("Padding:" + str(4-(rom_offset % 4))) rom_offset+=4 - (rom_offset % 4) ## Write Data sections #################################################################### for elt_file in element_file: if os.path.exists(elt_file): rd_modulo= os.path.getsize(elt_file) % 4 log("Add data section:"+ str(elt_file) +",size="+str(os.path.getsize(elt_file))+"at:"+str( os.path.getsize(rom_filename) )) with open(elt_file,"rb") as input_file: out_file.write( input_file.read()) if rd_modulo != 0: while (rd_modulo != 4): out_file.write(pack("c", b'P')) rd_modulo=rd_modulo+1 log("Write Padding") if COMPRESS_WITH == ZLIB_COMPRESSOR: ## Compress ROM file using zlib c = zlib.compressobj(level=9, method=zlib.DEFLATED, wbits=-15, memLevel=9) compressed_rom = c.compress(Path(rom_filename).read_bytes()) + c.flush() ## Compress ROM file using zopfli #import zopfli #c = zopfli.ZopfliCompressor(zopfli.ZOPFLI_FORMAT_DEFLATE) #compressed_rom = c.compress(Path(rom_filename).read_bytes()) + c.flush() elif COMPRESS_WITH == LZ4_COMPRESSOR: ## Compress ROM using LZ4 compressed_rom = lz4.compress( Path(rom_filename).read_bytes(), compression_level=9, block_size=lz4.BLOCKSIZE_MAX1MB, block_linked=False,) elif COMPRESS_WITH == LZMA_COMPRESSOR: ## Compress ROM using LZMA import lzma compressed_rom = lzma.compress( Path(rom_filename).read_bytes(), format=lzma.FORMAT_ALONE, filters=[ { "id": lzma.FILTER_LZMA1, "preset": 6, "dict_size": 16 * 1024, } ]) compressed_rom = compressed_rom[13:] # fix windows issue due to ':' in file name final_rom_filename = final_rom_filename.replace(':','') with open(final_rom_filename, "wb") as out_file: if COMPRESS_WITH == ZLIB_COMPRESSOR: out_file.write( b'ZLIB') out_file.write(pack(" BUILD SUCCESS '+ final_rom_filename+' size:'+str(final_rom_size)) printProgressBar(bar_progress, bar_total, prefix = bar_prefix, suffix = 'COMPLETE ') if not DEBUG: print("")