Compare commits

...

10 commits

Author SHA1 Message Date
jaseg
b9dfb036d4 Merge branch 'working'
Conflicts:
	pixelterm/gifterm.py
2015-02-17 19:49:41 +01:00
jaseg
fa2ce0e09d script reorganization 2015-02-17 19:48:03 +01:00
jaseg
22a0374ead Fixed setup.py for console usage 2014-03-26 12:20:43 +01:00
jaseg
67a0cf000a Fixed crashes of gifterm when displaying certain GIFs 2014-03-26 12:14:35 +01:00
jaseg
755b369ff9 Reduced flickering 2014-01-13 21:52:22 +01:00
jaseg
af5f0f28b1 Adjusted the maximum frame rate 2014-01-13 20:57:59 +01:00
jaseg
37da26d1a1 Added --serve TCP server to gifterm 2014-01-12 19:24:24 +01:00
jaseg
36068c3d47 Import fixes and a small terminal size handling fix 2014-01-12 19:07:12 +01:00
jaseg
2fb648b3d6 Update README install section 2014-01-12 19:00:29 +01:00
jaseg
74379648d3 Terminal size override fix. 2014-01-12 18:59:14 +01:00
10 changed files with 235 additions and 221 deletions

View file

@ -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
-----

197
commands.py Executable file
View file

@ -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()]))

View file

@ -1,7 +1,7 @@
# Maintainer: jaseg <arch@jaseg.net>
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')

View file

@ -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()

View file

@ -1,57 +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'
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]')
args = parser.parse_args()
tw, th = os.get_terminal_size()
th = th*2
if args.size:
tw, th = map(int, args.size.split('x'))
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['transparency']:
last_frame.paste(c, c)
else:
last_frame = c
im = last_frame.copy()
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))
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
if __name__ == '__main__':
main()

48
pixelterm/pixelterm.py Normal file → Executable file
View file

@ -1,10 +1,10 @@
#!/usr/bin/env python
from pixelterm import xtermcolors
from pixelterm.xtermcolors import closest_color
reset_sequence = '\033[39;49m'
def termify_pixels(img):
def termify_pixels(img, fill=False):
sx, sy = img.size
out = ''
@ -20,7 +20,7 @@ def termify_pixels(img):
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):
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):
@ -66,44 +66,6 @@ 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():
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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -4,49 +4,49 @@ 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'],
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,
classifiers = [
'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',
'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 = [],
)