diff --git a/README.md b/README.md index a93604a..53c3308 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,14 @@ MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNs. -hMMMMMMMMMMMMMMMMMMMMMMMMM MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMdyymMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM ``` ``` -cp437 ansi parsing - this will be integrated into bots like krylon/maple/g1mp when finished. +dr1p4ns1 v1.2 - cp437 ansi parsing - this logic will be rolled into bots like krylon/maple/g1mp ``` ## prerequisites - `apt install python3` ## usage - main file ``` running 'python3 dr1p4ns1.py' will batch process all the {ans,asc} files in the ansiscii directory +running 'python3 dr1p4ns1.py /path/to/ansi/files' will load all the .ans files in that directory ``` ## test data and debugging - running `python3 test.py` will present table data and create an ansi file for testing @@ -47,18 +48,11 @@ running 'python3 dr1p4ns1.py' will batch process all the {ans,asc} files in the ```d=dr1p4ns1(ansifile=_FILE_,width=80,debug=False)``` ## files overview ``` -/access.log - after usage the files laded or with error are logged into this file /dr1p4ns1.py - main file, reads 'work.ans' and attempts to parse and display +/ansiscii/ - storing the ansi and ascii graphics in this subdirectory while testing /test.py - used to generate test ansi file and presents table data /test.ans - thie file is created after running test.py to contain controlled test data -/ansiscii/ - storing the ansi and ascii graphics in this subdirectory while testing -/ansiscii/work.ans - this ansi file is loaded by 'dr1p4ns1.py', this is the logic testing -/ansiscii/work.asc - escape codes/colors stripped in text for test referencing ``` ## main files of interest - dr1p4ns1.py -- work.ans -- work.asc -- test.py -- test.ans ``` diff --git a/dr1p4ns1.py b/dr1p4ns1.py index bc88a99..f8e3443 100644 --- a/dr1p4ns1.py +++ b/dr1p4ns1.py @@ -2,7 +2,7 @@ #################################################################################################### ####################### from glob import glob from time import sleep -import os +import sys,tty,os,termios """MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM ####################### MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMh+MMMMMMMMMMMMMMhsMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM ####################### MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMm/ oMMMMMMMMMMMMMMm +NMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM ####################### @@ -81,7 +81,7 @@ class dr1p4ns1: 'm': 'color', 'R': 'report_cursor_position'} ############################################################################################################ LOOKUP TABLE - asc_table=""" + asc_table="""\ ╟ⁿΘΓΣαστΩδΦ∩ε∞─┼╔µ╞⌠÷≥√∙ ╓▄óúÑ₧ƒ\ ßφ≤·±╤¬║┐⌐¼╜╝í½╗░▒▓│┤╡╢╖╕╣║╗╝╜╛┐\ └┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀\ @@ -90,10 +90,24 @@ class dr1p4ns1: for r in (('\n',''),(' ','')): asc_table=asc_table.replace(*r) ############################################################################################################ LOOKUP TABLE + asc_table_full="""\ + \x00☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼\ + \_!"#$%&\'()*+,-./0123456789:;<=>\ + ?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^\ + _`abcdefghijklmnopqrstuvwxyz{|}~\ + ⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧\ + ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛\ + ┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐\ + ▀ɑϐᴦᴨ∑ơµᴛɸϴΩẟ∞∅∈∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■\ + ÿ""" + ##################################################################################################### ASCII TABLE CLEANUP + for r in ((' ',''),('\_',' ')): + asc_table_full=asc_table_full.replace(*r) + ############################################################################################################ LOOKUP TABLE asc2uni=dict(zip([i for i in range(128,256)],[ord(c) for c in asc_table])) ############################################################################################################ LOOKUP TABLE asc2chr=dict(zip([i for i in range(128,256)],[c for c in asc_table])) - ############################################################################################################ LOOKUP TABLE + ############################################################################################################ LOOKUP TABLE rgb2irc = { 52: '16', 94: '17', 100: '18', 58: '19', 22: '20', 29: '21', 23: '22', 24: '23', 17: '24', 54: '25', 53: '26', 89: '27', @@ -122,10 +136,12 @@ class dr1p4ns1: self.height=height self.filename=ansifile self.openansi(ansifile) - self.openascii() + #self.openascii() self.code='\x1b[0m' self.cmps=[] - print("\x1bc\x1b[1;31m[ DR1P ]\x1b[0m\n") + self.op=[] + self.optype=[] + self.sauce_found=False self.boot() ######################################################################################################### FILE OPERATIONS def fifo(self,s): @@ -194,9 +210,6 @@ class dr1p4ns1: print(f"{hex(16 * _)[2:].zfill(8)} {thehex[_]} {theasc[_]}") ############################################################################################################### DEBUGGING def hexdump(self): - if self.DEBUG: - print("\n\x1b[1;31m[ HEXDUMP ]\x1b[0m\n") - self.printhex() self.codes=[] code_start=0 code_end=0 @@ -216,25 +229,173 @@ class dr1p4ns1: code_end=i+1 code=self.ansifile[code_start:code_end] self.codes.append([code,code_start,code_end]) + ############################################################################################################### DEBUGGING + def codedump(self): + xpos_old=0 + xpos_new=0 + self.codes=[] + code_start=0 + code_end=0 + code_state=False + for i,_ in enumerate(self.ansifile): + if code_state==False: + if _ == '\x1b': + code_state=True + code_start=i + elif _ == '\x1a': + self.op.append(self.ansifile[code_end:i]) + code=self.ansifile[i:] + self.codes.append([code,i,len(self.ansifile)]) + if code[1:6].upper()=="SAUCE": + self.getsauce(code[1:]) + else: + if 64 <= ord(_) <= 126: + try: + if _ in self.commands: + code_state=False + code_end=i+1 + code=self.ansifile[code_start:code_end] + self.codes.append([code,code_start,code_end]) + xpos_new=code_start + distance=xpos_new-xpos_old + if not distance == 0: + nonop=self.ansifile[xpos_old:xpos_old+distance] + self.op.append(nonop) + self.optype.append(1) + self.op.append(code) + xpos_old=code_end + if self.commands[_] == 'color': + self.optype.append(2) + elif self.commands[_] == 'forward': + self.optype.append(3) + else: + self.optype.append(0) + except Exception as e: + print(f'error: {e}') + if not self.sauce_found: + code=self.ansifile[i:] + #################################################################################################################### TOOL + def processdump(self): + offset=0 + offsets=[] + processed=[] + uniqued=[] + uniques=[] + processing='' + for i,_optype in enumerate(self.optype): + if _optype==2: + processing+=self.op[i] + offset+=len(self.op[i]) + elif _optype==3: + count=int(self.op[i].split('[')[1].split('C')[0]) + processing+=' '*count + elif _optype==1: + processing+=self.op[i] + _bseq='\r\n' + _xpos=processing.find(_bseq) + distance=0 + while not processing.find(_bseq) == -1: + _xpos=processing.find(_bseq) + if not _xpos == -1: + uniques.append(_bseq) + uniques.append(_xpos) + if len(self.stripcodes(processing[:_xpos])) >= 80: distance=80 + gaps=self.width-len(self.stripcodes(processing)[distance:].split('\r\n')[0]) + processing=processing.replace(_bseq,chr(32)*gaps,1) + if len(processing)-offset > self.width: + trailing_size=0 + while len(processing)-offset > self.width: + deconcatenate=processing[0:self.width+offset] + if deconcatenate.endswith('m'): + if not deconcatenate.rfind('\x1b')==-1: + trailing_size=len(deconcatenate[deconcatenate.rfind('\x1b'):]) + deconcatenate=deconcatenate[:deconcatenate.rfind('\x1b')] + processed.append(deconcatenate) + if len(uniques) < 2: + uniqued.append('') + else: + uniqued.append(uniques) + uniqued.append(uniques) + uniques=[] + offsets.append(offset) + processing=processing.replace(deconcatenate,'',1) + offset=0+trailing_size + if len(processing) > 0: + gaps=int(80-len(self.stripcodes(processing))) + processing+=chr(32)*gaps + processed.append(processing) + + processed_colors=[]; last_colors=''; color_fg=''; color_bg=''; color_set=''; + for y,__ in enumerate(processed): + code_start=0; code_end=0; code_state=False; codes=[]; + for x,_ in enumerate(__): + if not code_state: + if _ =='\x1b': + code_start = x + code_state = True + else: + if _ in self.commands: + if self.commands[_] == 'color': + code_end = x+1 + code_state = False + codes.append(__[code_start:code_end]) + for code in codes: + for _ in code.replace('m','').split('[')[1].split(';'): + if 0 <= int(_) <= 9: + color_set=_ + if 30 <= int(_) <= 39: + color_fg=_ + if 40 <= int(_) <= 49: + color_bg=_ + colors=f"{color_set}" + processed_colors.append(colors) + + # last_processed_color_set=0 + # for i,_ in enumerate(processed): + # line='' + # if last_processed_color_set: + # line+='\x1b[1m' + # if int(processed_colors[i]): + # line+=f'{_}\x1b[0m' + # else: + # line+=f'{_}' + # last_processed_color_set=int(processed_colors[i]) + # for r in (('\x16',chr(9644)),('\x15','\u00A7')): + # line=line.replace(*r) + # print(line+f" - {str(i).zfill(3)}") + + for _ in processed: + line=_ + for r in (('\x16',chr(9644)),('\x15','\u00A7')): + line=line.replace(*r) + print(line) + + #################################################################################################################### TOOL + def getsauce(self,sauce): + self.sauce=sauce + self.sauce_found=True + self.width=ord(self.sauce[96]) + self.height=ord(self.sauce[98]) + offset=len("SAUCE") + self.sauce_version=str(int(self.sauce[offset:offset+2].strip())) + offset+=2 + self.sauce_title=self.sauce[offset:offset+35].strip() + offset+=35 + self.sauce_title=self.sauce[offset:offset+20].strip() + offset+=20 + self.sauce_group=self.sauce[offset:offset+20].strip() + offset+=20 + self.sauce_date=self.sauce[offset:offset+8].strip() + offset+=8 + # SAUCE_PASS='\x1b[1;31m[ SAUCE INFO FOUND ] - X: {} Y: {}\x1b[0m\n' + # SAUCE_FAIL='\x1b[1;31m[ NO SAUCE INFO FOUND ]\x1b[0m\n' + # if self.sauce_found==True: + # print(SAUCE_PASS.format(self.width,self.height)) + # else: + # print(SAUCE_FAIL.format(self)) #################################################################################################################### TOOL def findall(self,s,w): return [i for i in range(len(s)) if s.startswith(w, i)] - #################################################################################################################### TOOL - def getsauce(self): - SPASS='\x1b[1;31m[ SAUCE INFO FOUND ] - X: {} Y: {} - FILE: {}\x1b[0m\n' - SFAIL='\x1b[1;31m[ NO SAUCE INFO FOUND ] - FILE: {}\x1b[0m\n' - try: - if self.ansifile[-129:][0] == '\x1a' \ - and self.ansifile[-128:][:5] == 'SAUCE': - self.ansifile_xwidth=ord(self.ansifile[-128:][96]) - self.ansifile_yheight=ord(self.ansifile[-128:][98]) - self.width=self.ansifile_xwidth - self.height=self.ansifile_yheight - print(SPASS.format(self.width,self.height,self.filename)) - else: - print(SFAIL.format(self.filename)) - except: - print(SFAIL.format(self.filename)) ############################################################################################################### DEBUGGING def cmpans(self,s1,s2,n1=0,n2=0): _s1=''; _s2=''; _sn1=''; _sn2='' @@ -300,104 +461,71 @@ class dr1p4ns1: return {v: k for k, v in d.items()} ########################################################################################################## CLASS MAIN - 4 def boot(self): - self.getsauce() - self.hexdump() - op=[] - fb=self.ansifile - xnew=0 - xold=0 - for _ in self.codes: - xnew=_[1] - distance=xnew-xold - if not distance == 0: - gap=fb[xold:xold+distance] - op.append(gap) - op.append(_[0]) - xold=_[2] - optype=[] - for i,_op in enumerate(op): - if _op.startswith('\x1b'): - if _op.endswith('m'): - optype.append(2) - elif _op.endswith('C'): - optype.append(3) - else: - optype.append(0) - elif _op.startswith('\x1a'): - optype.append(4) + if self.DEBUG: + print("\n\x1b[1;31m[ HEXDUMP ]\x1b[0m\n") + self.printhex() + self.codedump() + self.processdump() +############################################################################################################################# +def getkey(): + old_settings = termios.tcgetattr(sys.stdin) + tty.setcbreak(sys.stdin.fileno()) + try: + while True: + b = os.read(sys.stdin.fileno(), 3).decode() + if len(b) == 3: + k = ord(b[2]) else: - optype.append(1) - offset=0 - offsets=[] - processed=[] - uniqued=[] - uniques=[] - processing='' - for i,_optype in enumerate(optype): - if _optype==2: - processing+=op[i] - offset+=len(op[i]) - elif _optype==3: - count=int(op[i].split('[')[1].split('C')[0]) - processing+=' '*count - elif _optype==1: - processing+=op[i] - _bseq='\r\n' - _xpos=processing.find(_bseq) - if not _xpos == -1: - uniques.append(_bseq) - uniques.append(_xpos) - processing=processing.replace(_bseq,'',1) - gaps=self.width-len(self.stripcodes(processing)) - processing+=' '*int(gaps) - if len(processing)-offset > self.width: - while len(processing)-offset > self.width: - deconcatenate=processing[0:self.width+offset] - processed.append(deconcatenate) - if len(uniques) < 2: - uniqued.append('') - else: - uniqued.append(uniques) - uniqued.append(uniques) - uniques=[] - offsets.append(offset) - processing=processing.replace(deconcatenate,'',1) - offset=0 - # if self.DEBUG: - # print("\n\x1b[1;31m[ COMPARE LINE AGAINST REFERENCE ]\x1b[0m\n") - # _r=self.reference.strip().splitlines() - # for i,_ in enumerate(processed): - # s1=_r[i] - # s2=_ - # self.cmpans(s1=s1,s2=s2,n1=0,n2=self.width) - # if i >= 128: - # break - # for i,_ in enumerate(self.cmps): - # print(_+f' - {str(i).zfill(4)}') - # if self.DEBUG: - # print("\n \x1b[1;31m[ PROCESSED ANSI ]\x1b[0m\n") - for i,_ in enumerate(processed): - print(_) + k = ord(b) + key_mapping = { + 27: 'esc', + 67: 'right', + 68: 'left', + 113: 'q', + 120: 'x' + } + return key_mapping.get(k, chr(k)) + finally: + termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings) ################################################################################################################### ENTRY - 1 if __name__ == "__main__": + if len(sys.argv) > 1: + PATH='' + if sys.argv[1]=='.': + PATH=os.getcwd() + files=glob(PATH+"/*.ans") + files+=glob(PATH+"/*.ANS") + else: + files=glob(sys.argv[1]+"/*.ans") + files+=glob(sys.argv[1]+"/*.ANS") + else: + files=glob('ansiscii/*.ans') + files+=glob('ansiscii/*.ANS') + + if len(files)==0: + print('ERROR: NO ANSI FILES IN THE PATH SPECIFIED',end='') + sys.exit(1) + + index=0 + d=dr1p4ns1(ansifile=files[index],width=80,debug=False) try: - os.remove('access.log') - except: - pass - files=glob('ansiscii/*.ans') - files+=glob('ansiscii/*.ANS') - for _FILE_ in files: - print(f'\n[ LOADING FILE ] - {_FILE_}\n') - try: - d=dr1p4ns1(ansifile=_FILE_,width=80,debug=False) - f=open('access.log','a') - f.write(f'LOADED: {_FILE_}\n') - f.close() - except Exception as e: - print(f'[ ERROR WITH FILE: {_FILE_} ] - {e}') - f=open('access.log','a') - f.write(f'FAILED: {_FILE_} - {e}\n') - f.close() - del(d) - sleep(5) + while True: + B='\x1b[1;94m'; C='\x1b[1;94m'; S='\x1b[1;36m'; M='\x1b[1;92m'; E='\x1b[0m' + msg=f"{B}[ {C}DR1P {B}] {S}- {B}[ {C}q{S}: {M}quit{S}, {C}left{S}: {M}previous{S}, {C}right{S}: {M}next {B}] {S}- {C}filename{S}: {M}" + msg+=f"{d.filename}{E}" + print(msg) + k = getkey() + if k == 'left': + index-=1 + if index < 0: index=len(files)-1 + d=dr1p4ns1(ansifile=files[index],width=80,debug=False) + if k == 'right': + index+=1 + if index >= len(files): index=0 + d=dr1p4ns1(ansifile=files[index],width=80,debug=False) + if k == 'esc' or k == 'q' or k == 'x': + quit() + except (KeyboardInterrupt, SystemExit): + os.system('stty sane') + ######################################################################################################################### EOF \ No newline at end of file