diff options
-rwxr-xr-x | commands.py | 197 | ||||
-rw-r--r-- | pixelterm/__init__.py | 1 | ||||
-rwxr-xr-x | pixelterm/colorcube.py | 25 | ||||
-rwxr-xr-x | pixelterm/gifterm.py | 89 | ||||
-rwxr-xr-x | pixelterm/pixelterm.py | 44 | ||||
-rwxr-xr-x | pixelterm/pngmeta.py | 16 | ||||
-rwxr-xr-x | pixelterm/resolvecolor.py | 17 | ||||
-rw-r--r-- | pixelterm/unpixelterm.py | 34 | ||||
-rwxr-xr-x | setup.py | 14 |
9 files changed, 209 insertions, 228 deletions
diff --git a/commands.py b/commands.py new file mode 100755 index 0000000..fc35c54 --- /dev/null +++ b/commands.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python + +import os, sys, argparse, os.path, json, time, signal, atexit +from collections import defaultdict +from pixelterm.pixelterm import termify_pixels, reset_sequence +from pixelterm.unpixelterm import unpixelterm +from PIL import Image, PngImagePlugin, GifImagePlugin, ImageSequence +import re + + +def pixelterm(): + import os, sys, argparse, os.path, json + from multiprocessing.dummy import Pool + from PIL import Image, PngImagePlugin + + parser = argparse.ArgumentParser(description='Render pixel images on 256-color ANSI terminals') + parser.add_argument('image', type=str, nargs='*') + parser.add_argument('-d', '--output-dir', type=str, help='Output directory (if not given, output to stdout)') + args = parser.parse_args() + + def convert(f): + img = Image.open(f).convert("RGBA") + if args.output_dir: + print(f) + foo, _, _ = f.rpartition('.png') + output = os.path.join(args.output_dir, os.path.basename(foo)+'.pony') + metadata = json.loads(img.info.get('pixelterm-metadata')) + comment = metadata.get('_comment') + if comment is not None: + del metadata['_comment'] + comment = '\n'+comment + else: + comment = '' + metadataarea = '$$$\n' +\ + '\n'.join([ '\n'.join([ k.upper() + ': ' + v for v in metadata[k] ]) for k in sorted(metadata.keys()) ]) +\ + comment + '\n$$$\n' + with open(output, 'w') as of: + of.write(metadataarea) + of.write(termify_pixels(img)) + else: + print(termify_pixels(img)) + + p = Pool() + p.map(convert, args.image) + + +def unpixelterm(): + import argparse, json + + parser = argparse.ArgumentParser(description='Convert images rendered by pixelterm-like utilities back to PNG') + parser.add_argument('-v', '--verbose', action='store_true') + output_group = parser.add_mutually_exclusive_group() + output_group.add_argument('-o', '--output', type=str, help='Output file name, defaults to ${input%.pony}.png') + output_group.add_argument('-d', '--output-dir', type=str, help='Place output files here') + parser.add_argument('input', type=argparse.FileType('r'), nargs='+') + args = parser.parse_args() + if len(args.input) > 1 and args.output: + parser.print_help() + print('You probably do not want to overwrite the given output file {} times.'.format(len(args.input))) + sys.exit(1) + + for f in args.input: + if len(args.input) > 1: + print(f.name) + img, metadata = unpixelterm(f.read()) + pnginfo = PngImagePlugin.PngInfo() + pnginfo.add_text('pixelterm-metadata', json.dumps(metadata)) + foo, _, _ = f.name.rpartition('.pony') + output = args.output or foo+'.png' + if args.output_dir: + output = os.path.join(args.output_dir, os.path.basename(output)) + img.save(output, 'PNG', pnginfo=pnginfo) + + +clear_screen = '\033[H\033[2J' +home_cursor = '\033[H' +cursor_invisible = '\033[?25l' +cursor_visible = '\033[?25h' + +def gifterm(): + parser = argparse.ArgumentParser(description='Render pixel images on 256-color ANSI terminals') + parser.add_argument('image', type=str) + parser.add_argument('-s', '--size', type=str, help='Terminal size, [W]x[H]') + parser.add_argument('--serve', type=int, help='Serve via TCP on given port') + args = parser.parse_args() + + tw, th = None, None + if args.size: + tw, th = map(int, args.size.split('x')) + else: + try: + tw, th = os.get_terminal_size() + except: # If this is not a regular terminal + pass + th = th*2 + + img = Image.open(args.image) + palette = img.getpalette() + last_frame = Image.new("RGBA", img.size) + frames = [] + + for frame in ImageSequence.Iterator(img): + #This works around a known bug in Pillow + #See also: http://stackoverflow.com/questions/4904940/python-converting-gif-frames-to-png + frame.putpalette(palette) + c = frame.convert("RGBA") + + if img.info['background'] != img.info.get('transparency'): + last_frame.paste(c, c) + else: + last_frame = c + + im = last_frame.copy() + if (tw, th) != (None, None): + im.thumbnail((tw, th), Image.NEAREST) + frames.append(termify_pixels(im, True)) + + if args.serve: + from socketserver import ThreadingMixIn, TCPServer, BaseRequestHandler + + # Quote-Of-The-Day protocol implementation + # See RFC865 ( https://tools.ietf.org/html/rfc865 ) for details. + + class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass + + class QOTDHandler(BaseRequestHandler): + def handle(self): + try: + self.request.sendall(bytes(cursor_invisible, "UTF-8")) + while True: + for frame in frames: + self.request.sendall(bytes(home_cursor + reset_sequence, "UTF-8")) + self.request.sendall(bytes(frame, "UTF-8")) + time.sleep(min(1/10, img.info['duration']/1000.0)) + except: + pass + + server = ThreadingTCPServer(('', args.serve), QOTDHandler) + server.serve_forever() + else: + print(cursor_invisible) + atexit.register(lambda:print(cursor_visible)) + signal.signal(signal.SIGTERM, lambda signum, stack_frame: exit(1)) + + try: + while True: + for frame in frames: + print(home_cursor) + print(reset_sequence) + print(frame) + time.sleep(min(1/10, img.info['duration']/1000.0)) + except KeyboardInterrupt: + pass + + +# Display an xterm-256color color palette on the terminal, including color ids + +reset_sequence = '\033[39;49m' + +def _esc(i): + return '\033[48;5;'+str(i)+'m' + +def colorcube(): + print(''.join([str(i).ljust(4) for i in range(16)])) + print(' '.join([_esc(i) for i in range(16)])+' ' + reset_sequence) + + for j in range(6): + for k in range(6): + c = 16+j*6+k*6*6 + print(''.join([str(c+i).ljust(4) for i in range(6)])) + print(' '.join([_esc(c+i) for i in range(6)])+' ' + reset_sequence) + + print(''.join([str(i).ljust(4) for i in range(16+6*6*6, 16+6*6*6+24)])) + print(' '.join([_esc(i) for i in range(16+6*6*6, 16+6*6*6+24)])+' ' + reset_sequence) + + +def pngmeta(): + parser = argparse.ArgumentParser(description='Print PNG metadata') + parser.add_argument('image', type=str) + args = parser.parse_args() + img = Image.open(args.image) + for k, v in img.info.items(): + print('{:15}: {}'.format(k, v)) + + +def resolvecolor(): + import os, sys, argparse, os.path, json, re + from pixelterm.xtermcolors import closest_color + + # Resolve HTML-style hex RGB color codes to xterm-256color color numbers + + if len(sys.argv) != 2: + print('Usage: resolvecolor.py #RRGGBB') + exit() + + print(closest_color(*[int(s, 16) for s in re.match('#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})', sys.argv[1]).groups()])) + diff --git a/pixelterm/__init__.py b/pixelterm/__init__.py index 8692824..e69de29 100644 --- a/pixelterm/__init__.py +++ b/pixelterm/__init__.py @@ -1 +0,0 @@ -__all__ = ['xtermcolors', 'pixelterm'] diff --git a/pixelterm/colorcube.py b/pixelterm/colorcube.py deleted file mode 100755 index b7fe65c..0000000 --- a/pixelterm/colorcube.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python - -# Display an xterm-256color color palette on the terminal, including color ids - -reset_sequence = '\033[39;49m' - -def esc(i): - return '\033[48;5;'+str(i)+'m' - -def main(): - print(''.join([str(i).ljust(4) for i in range(16)])) - print(' '.join([esc(i) for i in range(16)])+' ' + reset_sequence) - - for j in range(6): - for k in range(6): - c = 16+j*6+k*6*6 - print(''.join([str(c+i).ljust(4) for i in range(6)])) - print(' '.join([esc(c+i) for i in range(6)])+' ' + reset_sequence) - - print(''.join([str(i).ljust(4) for i in range(16+6*6*6, 16+6*6*6+24)])) - print(' '.join([esc(i) for i in range(16+6*6*6, 16+6*6*6+24)])+' ' + reset_sequence) - -if __name__ == '__main__': - main() - diff --git a/pixelterm/gifterm.py b/pixelterm/gifterm.py deleted file mode 100755 index 23380a0..0000000 --- a/pixelterm/gifterm.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python - -import os, sys, argparse, os.path, json, time, signal, atexit -from pixelterm import pixelterm -from PIL import Image, GifImagePlugin, ImageSequence - -clear_screen = '\033[H\033[2J' -home_cursor = '\033[H' -cursor_invisible = '\033[?25l' -cursor_visible = '\033[?25h' - -def main(): - parser = argparse.ArgumentParser(description='Render pixel images on 256-color ANSI terminals') - parser.add_argument('image', type=str) - parser.add_argument('-s', '--size', type=str, help='Terminal size, [W]x[H]') - parser.add_argument('--serve', type=int, help='Serve via TCP on given port') - args = parser.parse_args() - - tw, th = None, None - if args.size: - tw, th = map(int, args.size.split('x')) - else: - try: - tw, th = os.get_terminal_size() - except: # If this is not a regular terminal - pass - th = th*2 - - img = Image.open(args.image) - palette = img.getpalette() - last_frame = Image.new("RGBA", img.size) - frames = [] - - for frame in ImageSequence.Iterator(img): - #This works around a known bug in Pillow - #See also: http://stackoverflow.com/questions/4904940/python-converting-gif-frames-to-png - frame.putpalette(palette) - c = frame.convert("RGBA") - - if img.info['background'] != img.info.get('transparency'): - last_frame.paste(c, c) - else: - last_frame = c - - im = last_frame.copy() - if (tw, th) != (None, None): - im.thumbnail((tw, th), Image.NEAREST) - frames.append(pixelterm.termify_pixels(im, True)) - - if args.serve: - from socketserver import ThreadingMixIn, TCPServer, BaseRequestHandler - - # Quote-Of-The-Day protocol implementation - # See RFC865 ( https://tools.ietf.org/html/rfc865 ) for details. - - class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass - - class QOTDHandler(BaseRequestHandler): - def handle(self): - try: - self.request.sendall(bytes(cursor_invisible, "UTF-8")) - while True: - for frame in frames: - self.request.sendall(bytes(home_cursor + pixelterm.reset_sequence, "UTF-8")) - self.request.sendall(bytes(frame, "UTF-8")) - time.sleep(min(1/10, img.info['duration']/1000.0)) - except: - pass - - server = ThreadingTCPServer(('', args.serve), QOTDHandler) - server.serve_forever() - else: - print(cursor_invisible) - atexit.register(lambda:print(cursor_visible)) - signal.signal(signal.SIGTERM, lambda signum, stack_frame: exit(1)) - - try: - while True: - for frame in frames: - print(home_cursor) - print(pixelterm.reset_sequence) - print(frame) - time.sleep(min(1/10, img.info['duration']/1000.0)) - except KeyboardInterrupt: - pass - -if __name__ == '__main__': - main() - diff --git a/pixelterm/pixelterm.py b/pixelterm/pixelterm.py index 334e500..8a9d0be 100755 --- a/pixelterm/pixelterm.py +++ b/pixelterm/pixelterm.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from pixelterm import xtermcolors +from pixelterm.xtermcolors import closest_color reset_sequence = '\033[39;49m' @@ -20,7 +20,7 @@ def termify_pixels(img, fill=False): if color in bgd: return bgd[color] r,g,b,_ = color - bgd[color] = '\033[48;5;'+str(xtermcolors.closest_color(r,g,b))+'m' + bgd[color] = '\033[48;5;'+str(closest_color(r,g,b))+'m' return bgd[color] def fgescape(color): @@ -29,7 +29,7 @@ def termify_pixels(img, fill=False): return '' fg=color r,g,b,_ = color - fgd[color] = '\033[38;5;'+str(xtermcolors.closest_color(r,g,b))+'m' + fgd[color] = '\033[38;5;'+str(closest_color(r,g,b))+'m' return fgd[color] def balloon(x,y): @@ -69,41 +69,3 @@ def termify_pixels(img, fill=False): out = (out.rstrip() if bg == (0,0,0,0) and not fill else out) + '\n' return out[:-1] + reset_sequence + '\n' -def main(): - import os, sys, argparse, os.path, json - from multiprocessing.dummy import Pool - from PIL import Image, PngImagePlugin - - parser = argparse.ArgumentParser(description='Render pixel images on 256-color ANSI terminals') - parser.add_argument('image', type=str, nargs='*') - parser.add_argument('-d', '--output-dir', type=str, help='Output directory (if not given, output to stdout)') - args = parser.parse_args() - - def convert(f): - img = Image.open(f).convert("RGBA") - if args.output_dir: - print(f) - foo, _, _ = f.rpartition('.png') - output = os.path.join(args.output_dir, os.path.basename(foo)+'.pony') - metadata = json.loads(img.info.get('pixelterm-metadata')) - comment = metadata.get('_comment') - if comment is not None: - del metadata['_comment'] - comment = '\n'+comment - else: - comment = '' - metadataarea = '$$$\n' +\ - '\n'.join([ '\n'.join([ k.upper() + ': ' + v for v in metadata[k] ]) for k in sorted(metadata.keys()) ]) +\ - comment + '\n$$$\n' - with open(output, 'w') as of: - of.write(metadataarea) - of.write(termify_pixels(img)) - else: - print(termify_pixels(img)) - - p = Pool() - p.map(convert, args.image) - -if __name__ == '__main__': - main() - diff --git a/pixelterm/pngmeta.py b/pixelterm/pngmeta.py deleted file mode 100755 index 5ddde98..0000000 --- a/pixelterm/pngmeta.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python - -import os, sys, argparse -from PIL import Image, PngImagePlugin - -def main(): - parser = argparse.ArgumentParser(description='Print PNG metadata') - parser.add_argument('image', type=str) - args = parser.parse_args() - img = Image.open(args.image) - for k, v in img.info.items(): - print('{:15}: {}'.format(k, v)) - -if __name__ == '__main__': - main() - diff --git a/pixelterm/resolvecolor.py b/pixelterm/resolvecolor.py deleted file mode 100755 index 7fab111..0000000 --- a/pixelterm/resolvecolor.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python - -def main(): - import os, sys, argparse, os.path, json, re - from pixelterm import xtermcolors - - # Resolve HTML-style hex RGB color codes to xterm-256color color numbers - - if len(sys.argv) != 2: - print('Usage: resolvecolor.py #RRGGBB') - exit() - - print(xtermcolors.closest_color(*[int(s, 16) for s in re.match('#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})', sys.argv[1]).groups()])) - -if __name__ == '__main__': - main() - diff --git a/pixelterm/unpixelterm.py b/pixelterm/unpixelterm.py index 46c0c83..1159197 100644 --- a/pixelterm/unpixelterm.py +++ b/pixelterm/unpixelterm.py @@ -2,7 +2,7 @@ import os, sys, os.path from collections import defaultdict -from pixelterm import xtermcolors +from pixelterm.xtermcolors import xterm_colors from PIL import Image, PngImagePlugin try: import re2 as re @@ -16,7 +16,7 @@ def parse_escape_sequence(seq): while i<len(codes): if codes[i] in [38, 48]: if codes[i+1] == 5: - c = xtermcolors.xterm_colors[codes[i+2]] + c = xterm_colors[codes[i+2]] fg, bg = (c, bg) if codes[i] == 38 else (fg, c) i += 2 elif codes[i] == 39: @@ -90,33 +90,3 @@ def unpixelterm(text): x, y = 0, y+2 return img, metadata -def main(): - import argparse, json - - parser = argparse.ArgumentParser(description='Convert images rendered by pixelterm-like utilities back to PNG') - parser.add_argument('-v', '--verbose', action='store_true') - output_group = parser.add_mutually_exclusive_group() - output_group.add_argument('-o', '--output', type=str, help='Output file name, defaults to ${input%.pony}.png') - output_group.add_argument('-d', '--output-dir', type=str, help='Place output files here') - parser.add_argument('input', type=argparse.FileType('r'), nargs='+') - args = parser.parse_args() - if len(args.input) > 1 and args.output: - parser.print_help() - print('You probably do not want to overwrite the given output file {} times.'.format(len(args.input))) - sys.exit(1) - - for f in args.input: - if len(args.input) > 1: - print(f.name) - img, metadata = unpixelterm(f.read()) - pnginfo = PngImagePlugin.PngInfo() - pnginfo.add_text('pixelterm-metadata', json.dumps(metadata)) - foo, _, _ = f.name.rpartition('.pony') - output = args.output or foo+'.png' - if args.output_dir: - output = os.path.join(args.output_dir, os.path.basename(output)) - img.save(output, 'PNG', pnginfo=pnginfo) - -if __name__ == '__main__': - main() - @@ -24,14 +24,14 @@ setup(name = 'pixelterm', url = 'https://github.com/jaseg/pixelterm', packages = ['pixelterm'], install_requires=['pillow'], + py_modules = [ 'commands' ], entry_points = {'console_scripts': [ - 'pixelterm=pixelterm.pixelterm:main', - 'unpixelterm=pixelterm.unpixelterm:main', - 'gifterm=pixelterm.gifterm:main', - 'colorcube=pixelterm.colorcube:main', - 'resolvecolor=pixelterm.resolvecolor:main', - 'pngmeta=pixelterm.pngmeta:main']}, - zip_safe = True, + 'pixelterm=commands:pixelterm', + 'unpixelterm=commands:unpixelterm', + 'gifterm=commands:gifterm', + 'colorcube=commands:colorcube', + 'resolvecolor=commands:resolvecolor', + 'pngmeta=commands:pngmeta']}, classifiers = [ 'Development Status :: 5 - Production/Stable', 'Environment :: Console', |