From 74379648d3a1442d5811cca47bb82f5d9db1ba4d Mon Sep 17 00:00:00 2001 From: jaseg Date: Sun, 12 Jan 2014 18:55:37 +0100 Subject: [PATCH 1/9] Terminal size override fix. --- packaging/PKGBUILD | 4 ++-- pixelterm/__init__.py | 1 + pixelterm/gifterm.py | 6 ++++-- pixelterm/pixelterm.py | 0 setup.py | 45 +++++++++++++++++++++++------------------- 5 files changed, 32 insertions(+), 24 deletions(-) mode change 100644 => 100755 pixelterm/pixelterm.py diff --git a/packaging/PKGBUILD b/packaging/PKGBUILD index 5be9b8f..23fc5b3 100644 --- a/packaging/PKGBUILD +++ b/packaging/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: jaseg -pkgname=pixelterm-jaseg-git -pkgver=0.52.6a1f990 +pkgname=pixelterm-git +pkgver=0.54.af9ab13 pkgrel=1 pkgdesc="Display images on a terminal. Even works with animated GIFs" arch=('any') diff --git a/pixelterm/__init__.py b/pixelterm/__init__.py index e69de29..8692824 100644 --- a/pixelterm/__init__.py +++ b/pixelterm/__init__.py @@ -0,0 +1 @@ +__all__ = ['xtermcolors', 'pixelterm'] diff --git a/pixelterm/gifterm.py b/pixelterm/gifterm.py index 85c0428..0ebded1 100755 --- a/pixelterm/gifterm.py +++ b/pixelterm/gifterm.py @@ -14,10 +14,12 @@ def main(): parser.add_argument('-s', '--size', type=str, help='Terminal size, [W]x[H]') args = parser.parse_args() - tw, th = os.get_terminal_size() - th = th*2 + tw, th = None, None if args.size: tw, th = map(int, args.size.split('x')) + else: + tw, th = os.get_terminal_size() + th = th*2 img = Image.open(args.image) palette = img.getpalette() diff --git a/pixelterm/pixelterm.py b/pixelterm/pixelterm.py old mode 100644 new mode 100755 diff --git a/setup.py b/setup.py index fef0600..7d835da 100755 --- a/setup.py +++ b/setup.py @@ -4,26 +4,31 @@ from setuptools import setup import os, os.path import sys -ver = "1.1" +ver = "1.2" def read(filename): - return open(os.path.join(os.path.dirname(__file__), filename)).read() + return open(os.path.join(os.path.dirname(__file__), filename)).read() if sys.version_info < (3,0): - print('Oops, only python >= 3.0 supported!') - sys.exit() + print('Oops, only python >= 3.0 supported!') + sys.exit() setup(name = 'pixelterm', - version = ver, - description = 'Render pixely images on your terminal. Now also with animated GIF support.', - license = 'BSD', - author = 'jaseg', - author_email = 'pixelterm@jaseg.net', - url = 'https://github.com/jaseg/pixelterm', + version = ver, + description = 'Render pixely images on your terminal. Now also with animated GIF support.', + license = 'BSD', + author = 'jaseg', + author_email = 'pixelterm@jaseg.net', + url = 'https://github.com/jaseg/pixelterm', packages = ['pixelterm'], - install_requires=['pillow'], + install_requires=['pillow'], + scripts = [ 'pixelterm/pixelterm.py', + 'pixelterm/unpixelterm.py', + 'pixelterm/gifterm.py', + 'pixelterm/colorcube.py', + 'pixelterm/resolvecolor.py'], entry_points = {'console_scripts': [ 'pixelterm=pixelterm.pixelterm:main', 'unpixelterm=pixelterm.unpixelterm:main', @@ -31,22 +36,22 @@ setup(name = 'pixelterm', 'colorcube=pixelterm.colorcube:main', 'resolvecolor=pixelterm.resolvecolor:main', 'pngmeta=pixelterm.pngmeta:main']}, - zip_safe = True, - classifiers = [ + zip_safe = True, + classifiers = [ 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Intended Audience :: Information Technology', + 'Environment :: Console', + 'Intended Audience :: Information Technology', 'Intended Audience :: Intended Audience :: End Users/Desktop', 'License :: Freely Distributable', 'License :: OSI Approved :: BSD License', - 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3', 'Topic :: Internet', 'Topic :: Graphics', - 'Topic :: System :: Networking' + 'Topic :: System :: Networking' 'Topic :: Text Processing :: Filters', 'Topic :: Utilities', - ], + ], - long_description = read('README.md'), - dependency_links = [], + long_description = read('README.md'), + dependency_links = [], ) From 2fb648b3d616494a34351eeee4d2ed544c2b793d Mon Sep 17 00:00:00 2001 From: jaseg Date: Sun, 12 Jan 2014 19:00:29 +0100 Subject: [PATCH 2/9] Update README install section --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 59a265a..c995fe5 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ support*. *for best results, use tmux (for some reason that reduces flickering) Installation ------------- -Type ```sudo make install```. Sorry, there is not ```setup.py``` yet. Feel free -to send a pull request. +----------- +Although there is a ``setup.py`` I have grown quite frustrated with setuptools +and pythons abysmal package handling. Any contributions are welcome. Usage ----- From 36068c3d475384aa427646e54229ddfde7d14aaf Mon Sep 17 00:00:00 2001 From: jaseg Date: Sun, 12 Jan 2014 19:07:12 +0100 Subject: [PATCH 3/9] Import fixes and a small terminal size handling fix --- pixelterm/gifterm.py | 10 +++++++--- pixelterm/pixelterm.py | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pixelterm/gifterm.py b/pixelterm/gifterm.py index 0ebded1..e3563f3 100755 --- a/pixelterm/gifterm.py +++ b/pixelterm/gifterm.py @@ -1,7 +1,7 @@ #!/usr/bin/env python import os, sys, argparse, os.path, json, time, signal, atexit -from pixelterm import pixelterm +import pixelterm from PIL import Image, GifImagePlugin, ImageSequence clear_screen = '\033[H\033[2J' @@ -18,7 +18,10 @@ def main(): if args.size: tw, th = map(int, args.size.split('x')) else: - tw, th = os.get_terminal_size() + try: + tw, th = os.get_terminal_size() + except: # If this is not a regular terminal + pass th = th*2 img = Image.open(args.image) @@ -38,7 +41,8 @@ def main(): last_frame = c im = last_frame.copy() - im.thumbnail((tw, th), Image.NEAREST) + if (tw, th) != (None, None): + im.thumbnail((tw, th), Image.NEAREST) frames.append(pixelterm.termify_pixels(im)) print(cursor_invisible) diff --git a/pixelterm/pixelterm.py b/pixelterm/pixelterm.py index dccd788..30af163 100755 --- a/pixelterm/pixelterm.py +++ b/pixelterm/pixelterm.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -from pixelterm import xtermcolors +import xtermcolors reset_sequence = '\033[39;49m' From 37da26d1a1cfab4412647c2ffbc3ccedb2c024b9 Mon Sep 17 00:00:00 2001 From: jaseg Date: Sun, 12 Jan 2014 19:24:24 +0100 Subject: [PATCH 4/9] Added --serve TCP server to gifterm --- pixelterm/gifterm.py | 46 +++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/pixelterm/gifterm.py b/pixelterm/gifterm.py index e3563f3..61098cd 100755 --- a/pixelterm/gifterm.py +++ b/pixelterm/gifterm.py @@ -12,6 +12,7 @@ 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 @@ -45,18 +46,41 @@ def main(): im.thumbnail((tw, th), Image.NEAREST) frames.append(pixelterm.termify_pixels(im)) - print(cursor_invisible) - atexit.register(lambda:print(cursor_visible)) - signal.signal(signal.SIGTERM, lambda signum, stack_frame: exit(1)) + if args.serve: + from socketserver import ThreadingMixIn, TCPServer, BaseRequestHandler - try: - while True: - for frame in frames: - print(clear_screen, pixelterm.reset_sequence) - print(frame) - time.sleep(img.info['duration']/1000.0) - except KeyboardInterrupt: - pass + # 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(clear_screen + pixelterm.reset_sequence, "UTF-8")) + self.request.sendall(bytes(frame, "UTF-8")) + time.sleep(min(1/20, 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(clear_screen, pixelterm.reset_sequence) + print(frame) + time.sleep(min(1/20, img.info['duration']/1000.0)) + except KeyboardInterrupt: + pass if __name__ == '__main__': main() From af5f0f28b155de04e331b880617c68d1c01748e6 Mon Sep 17 00:00:00 2001 From: jaseg Date: Mon, 13 Jan 2014 20:57:59 +0100 Subject: [PATCH 5/9] Adjusted the maximum frame rate --- pixelterm/gifterm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pixelterm/gifterm.py b/pixelterm/gifterm.py index 61098cd..03cbabd 100755 --- a/pixelterm/gifterm.py +++ b/pixelterm/gifterm.py @@ -62,7 +62,7 @@ def main(): for frame in frames: self.request.sendall(bytes(clear_screen + pixelterm.reset_sequence, "UTF-8")) self.request.sendall(bytes(frame, "UTF-8")) - time.sleep(min(1/20, img.info['duration']/1000.0)) + time.sleep(min(1/10, img.info['duration']/1000.0)) except: pass @@ -78,7 +78,7 @@ def main(): for frame in frames: print(clear_screen, pixelterm.reset_sequence) print(frame) - time.sleep(min(1/20, img.info['duration']/1000.0)) + time.sleep(min(1/10, img.info['duration']/1000.0)) except KeyboardInterrupt: pass From 755b369ff9a564af3bee5273ebc929e76dacf81d Mon Sep 17 00:00:00 2001 From: jaseg Date: Mon, 13 Jan 2014 21:52:22 +0100 Subject: [PATCH 6/9] Reduced flickering --- pixelterm/gifterm.py | 8 +++++--- pixelterm/pixelterm.py | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pixelterm/gifterm.py b/pixelterm/gifterm.py index 03cbabd..7ca25ed 100755 --- a/pixelterm/gifterm.py +++ b/pixelterm/gifterm.py @@ -5,6 +5,7 @@ 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' @@ -44,7 +45,7 @@ def main(): im = last_frame.copy() if (tw, th) != (None, None): im.thumbnail((tw, th), Image.NEAREST) - frames.append(pixelterm.termify_pixels(im)) + frames.append(pixelterm.termify_pixels(im, True)) if args.serve: from socketserver import ThreadingMixIn, TCPServer, BaseRequestHandler @@ -60,7 +61,7 @@ def main(): self.request.sendall(bytes(cursor_invisible, "UTF-8")) while True: for frame in frames: - self.request.sendall(bytes(clear_screen + pixelterm.reset_sequence, "UTF-8")) + 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: @@ -76,7 +77,8 @@ def main(): try: while True: for frame in frames: - print(clear_screen, pixelterm.reset_sequence) + print(home_cursor) + print(pixelterm.reset_sequence) print(frame) time.sleep(min(1/10, img.info['duration']/1000.0)) except KeyboardInterrupt: diff --git a/pixelterm/pixelterm.py b/pixelterm/pixelterm.py index 30af163..74fe9f1 100755 --- a/pixelterm/pixelterm.py +++ b/pixelterm/pixelterm.py @@ -4,7 +4,7 @@ import xtermcolors reset_sequence = '\033[39;49m' -def termify_pixels(img): +def termify_pixels(img, fill=False): sx, sy = img.size out = '' @@ -66,7 +66,7 @@ def termify_pixels(img): if colbot == coltop: c,te,be = cf,te,te out += te(coltop) + be(colbot) + c - out = (out.rstrip() if bg == (0,0,0,0) else out) + '\n' + out = (out.rstrip() if bg == (0,0,0,0) and not fill else out) + '\n' return out[:-1] + reset_sequence + '\n' def main(): From 67a0cf000a4bd01ae6b40241ad984856d3fedb1c Mon Sep 17 00:00:00 2001 From: jaseg Date: Wed, 26 Mar 2014 12:14:35 +0100 Subject: [PATCH 7/9] Fixed crashes of gifterm when displaying certain GIFs --- pixelterm/gifterm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pixelterm/gifterm.py b/pixelterm/gifterm.py index 7ca25ed..4c87978 100755 --- a/pixelterm/gifterm.py +++ b/pixelterm/gifterm.py @@ -37,7 +37,7 @@ def main(): frame.putpalette(palette) c = frame.convert("RGBA") - if img.info['background'] != img.info['transparency']: + if img.info['background'] != img.info.get('transparency'): last_frame.paste(c, c) else: last_frame = c From 22a0374ead2edc1bf56d3588c47c99f23bd21745 Mon Sep 17 00:00:00 2001 From: jaseg Date: Wed, 26 Mar 2014 12:16:47 +0100 Subject: [PATCH 8/9] Fixed setup.py for console usage --- pixelterm/gifterm.py | 2 +- pixelterm/pixelterm.py | 2 +- setup.py | 5 ----- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/pixelterm/gifterm.py b/pixelterm/gifterm.py index 4c87978..23380a0 100755 --- a/pixelterm/gifterm.py +++ b/pixelterm/gifterm.py @@ -1,7 +1,7 @@ #!/usr/bin/env python import os, sys, argparse, os.path, json, time, signal, atexit -import pixelterm +from pixelterm import pixelterm from PIL import Image, GifImagePlugin, ImageSequence clear_screen = '\033[H\033[2J' diff --git a/pixelterm/pixelterm.py b/pixelterm/pixelterm.py index 74fe9f1..334e500 100755 --- a/pixelterm/pixelterm.py +++ b/pixelterm/pixelterm.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -import xtermcolors +from pixelterm import xtermcolors reset_sequence = '\033[39;49m' diff --git a/setup.py b/setup.py index 7d835da..2a7a1ac 100755 --- a/setup.py +++ b/setup.py @@ -24,11 +24,6 @@ setup(name = 'pixelterm', url = 'https://github.com/jaseg/pixelterm', packages = ['pixelterm'], install_requires=['pillow'], - scripts = [ 'pixelterm/pixelterm.py', - 'pixelterm/unpixelterm.py', - 'pixelterm/gifterm.py', - 'pixelterm/colorcube.py', - 'pixelterm/resolvecolor.py'], entry_points = {'console_scripts': [ 'pixelterm=pixelterm.pixelterm:main', 'unpixelterm=pixelterm.unpixelterm:main', From fa2ce0e09d865f5c52f49b147333d9a2cc2da85f Mon Sep 17 00:00:00 2001 From: jaseg Date: Tue, 17 Feb 2015 19:25:53 +0100 Subject: [PATCH 9/9] script reorganization --- commands.py | 197 ++++++++++++++++++++++++++++++++++++++ pixelterm/__init__.py | 1 - pixelterm/colorcube.py | 25 ----- pixelterm/gifterm.py | 89 ----------------- pixelterm/pixelterm.py | 44 +-------- pixelterm/pngmeta.py | 16 ---- pixelterm/resolvecolor.py | 17 ---- pixelterm/unpixelterm.py | 34 +------ setup.py | 19 ++-- 9 files changed, 209 insertions(+), 233 deletions(-) create mode 100755 commands.py delete mode 100755 pixelterm/colorcube.py delete mode 100755 pixelterm/gifterm.py delete mode 100755 pixelterm/pngmeta.py delete mode 100755 pixelterm/resolvecolor.py 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 4c87978..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 -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 74fe9f1..8a9d0be 100755 --- a/pixelterm/pixelterm.py +++ b/pixelterm/pixelterm.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -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 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() - diff --git a/setup.py b/setup.py index 7d835da..1d42649 100755 --- a/setup.py +++ b/setup.py @@ -24,19 +24,14 @@ setup(name = 'pixelterm', url = 'https://github.com/jaseg/pixelterm', packages = ['pixelterm'], install_requires=['pillow'], - scripts = [ 'pixelterm/pixelterm.py', - 'pixelterm/unpixelterm.py', - 'pixelterm/gifterm.py', - 'pixelterm/colorcube.py', - 'pixelterm/resolvecolor.py'], + 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',