Image to ascii——又一个轮子

本文介绍了一种使用Python将图片转换为ASCII艺术的方法,并提供了两种不同的风格:黑客帝国风格和经典风格。通过调整字体大小和输出样式,可以创建出独特的ASCII图像效果。

直接上代码和两个demo,本来想把html直接粘这里结果完全乱掉了:

https://github.com/reverland/scripts/blob/master/python/i2a.py


#! /bin/env python
# -*- coding: utf-8 -*-

"""
Turn images into acsii.
"""

__author__ = 'Reverland (lhtlyy@gmail.com)'

import Image
import ImageOps
import sys


filename = 'a.jpg'


def makeHTMLbox(body, fontsize, imagesize):
    """takes one long string of words and a width(px) then put them in an HTML box"""
    boxStr = """<div style=\"font-size: %spx;line-height: 100%s; width: %s;background-color: rgb(0, 0, 0);border: 1px grey solid;text-align: center; overflow: hidden;\">%s</div>
    """
    return boxStr % (fontsize, '%', imagesize[0], body)


def makeHTMLascii(body, color):
    """take words and , and create an HTML word """
    #num = str(random.randint(0,255))
    # return random color for every tags
    color = 'rgb(%s, %s, %s)' % color
    # get the html data
    wordStr = '<span style=\"color:%s;float:left;\">%s</span>'
    return wordStr % (color, body)


def i2m(im, fontsize):
    """turn an image into ascii like matrix"""
    im = im.convert('L')
    im = ImageOps.autocontrast(im)
    im.thumbnail((im.size[0] / fontsize, im.size[1] / fontsize))
    string = ''
    colors = [(0, i, 0) for i in range(0, 256, 17)]
    words = '据说只有到了十五字才会有经验的'
    for y in range(im.size[1]):
        for x in range(im.size[0]):
            p = im.getpixel((x, y))
            i = 14
            while i >= 0:
                if p >= i * 17:
                    s = makeHTMLascii(words[3 * i:3 * (i + 1)], colors[i])
                    break
                i -= 1
            if x % im.size[0] == 0 and y > 0:
                s = s + '<br/>'
            string = string + s
    return string


def i2a(im, fontsize):
    """turn an image into ascii with colors"""
    im = im.convert('RGB')
    im = ImageOps.autocontrast(im)
    im.thumbnail((im.size[0] / fontsize, im.size[1] / fontsize))
    string = ''
    for y in range(im.size[1]):
        for x in range(im.size[0]):
            c = im.getpixel((x, y))
            # print c
            s = makeHTMLascii('翻', c)
            if x % im.size[0] == 0 and y > 0:
                s = s + '<br/>'
            string = string + s
    return string


def getHTMLascii(filename, fontsize, style='matrix', outputfile='a.html', scale=1):
    """Got html ascii image"""
    im = Image.open(filename)
    size = (int(im.size[0] * scale), int(im.size[1] * scale))
    im.thumbnail(size, Image.ANTIALIAS)
    if style == 'matrix':
        ascii = makeHTMLbox(i2m(im, fontsize), fontsize, im.size)
    elif style == 'ascii':
        ascii = makeHTMLbox(i2a(im, fontsize), fontsize, im.size)
    else:
        print "Just support ascii and matrix now, fall back to matrix"
        ascii = makeHTMLbox(i2m(im, fontsize), fontsize, im.size)
    with open(outputfile, 'wb') as f:
        f.write(ascii)
    return 1


if __name__ == '__main__':
    if sys.argv[1] == '--help' or sys.argv[1] == '-h':
        print """Usage:python i2a.py filename fontsize [optional-parameter]
        optional-parameter:
            scale -- between (0, 1)
            style -- matrix or ascii"""
    else:
        filename = sys.argv[1]
        try:
            fontsize = int(sys.argv[2])
        except:
            fontsize = int(raw_input('input fontsize please:'))
        try:
            scale = float(sys.argv[3])
        except:
            scale = 1
        try:
            style = sys.argv[4]
        except:
            style = 'matrix'
        getHTMLascii(filename, fontsize, scale=scale, style=style)

demo1 黑客帝国风格

http://reverland.org/matrix_demo.html

demo2 经典风格

http://reverland.org/ascii_demo.html

转载于:https://my.oschina.net/u/175377/blog/110149

这是我原本的代码,你提供的就少了这么多? handlers.py: # handlers.py import os import json import tornado.web import tornado.websocket from concurrent.futures import ThreadPoolExecutor import magic from urllib.parse import unquote import hashlib import logging import zipfile from io import BytesIO import time # ⚠️ 必须补上:之前缺少此导入,会导致 scan_directory 出错 # 导入外部模块 from config import RESOURCE_POOLS, save_config from file_utils import scan_pools, get_file_list, get_file_type, _format_size # 设置日志 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # 全局线程池 executor = ThreadPoolExecutor(max_workers=10) def format_size(size_bytes): """将字节大小转为可读字符串""" if size_bytes == 0: return "0 B" size_names = ["B", "KB", "MB", "GB", "TB"] i = 0 while size_bytes >= 1024 and i < len(size_names) - 1: size_bytes /= 1024.0 i += 1 return f"{size_bytes:.1f} {size_names[i]}" async def scan_directory(pool_name): """ 异步扫描指定资源池的根目录 """ pool_info = RESOURCE_POOLS.get(pool_name) if not pool_info: return [] base_path = pool_info["path"] if not os.path.exists(base_path): logger.warning(f"路径不存在: {base_path}") return [] entries = [] try: with os.scandir(base_path) as scanner: for entry in scanner: if entry.name.startswith('.'): continue rel_path = entry.name full_path = os.path.join(base_path, rel_path) is_dir = entry.is_dir() file_info = { "name": entry.name, "rel_path": rel_path, "is_dir": is_dir, "modified": time.strftime("%Y-%m-%d %H:%M", time.localtime(entry.stat().st_mtime)), } if not is_dir: stat = entry.stat() file_type = get_file_type(full_path) file_info.update({ "size": stat.st_size, "formatted_size": format_size(stat.st_size), "type": file_type }) entries.append(file_info) entries.sort(key=lambda x: (not x['is_dir'], x['name'].lower())) RESOURCE_POOLS[pool_name]["entries"] = entries except PermissionError as e: logger.error(f"权限不足: {base_path}: {e}") entries = [] except Exception as e: logger.error(f"扫描出错: {base_path}: {e}") entries = [] return entries class MainHandler(tornado.web.RequestHandler): """ 主页处理器:渲染 admin.html 或 user.html """ async def get(self): path = self.request.path if path == '/admin': template = 'admin.html' else: template = 'user.html' # ✅ 安全地将 RESOURCE_POOLS 转为 JSON 字符串传给模板 self.render( template, RESOURCE_POOLS=json.dumps(RESOURCE_POOLS, ensure_ascii=False) ) class WSHandler(tornado.websocket.WebSocketHandler): """ WebSocket 处理器:实现实时更新通知 """ clients = set() def open(self): self.clients.add(self) logger.info(f"WebSocket 连接建立,当前客户端数: {len(self.clients)}") # ✅ 新增:连接成功后立即发送一次当前状态 data = json.dumps(RESOURCE_POOLS, ensure_ascii=False) try: self.write_message(data) except Exception as e: logger.error(f"推送初始数据失败: {e}") def on_close(self): self.clients.discard(self) logger.info(f"WebSocket 连接关闭,剩余客户端数: {len(self.clients)}") @classmethod async def send_updates(cls): """ 向所有客户端广播最新的 RESOURCE_POOLS 数据 """ data = json.dumps(RESOURCE_POOLS, ensure_ascii=False) to_remove = [] for client in cls.clients: try: await client.write_message(data) except Exception as e: logger.warning(f"向客户端发送消息失败: {e}") to_remove.append(client) for client in to_remove: try: client.close() cls.clients.remove(client) except Exception as e: logger.error(f"清理客户端连接失败: {e}") class UpdatePoolHandler(tornado.web.RequestHandler): """ 更新资源池路径的接口 """ async def post(self): try: data = json.loads(self.request.body) pool_name = data.get("pool") new_path = data.get("path") if not pool_name or not new_path: self.write({"status": "error", "message": "参数缺失"}) return if pool_name not in RESOURCE_POOLS: self.write({"status": "error", "message": "资源池不存在"}) return new_path = new_path.strip().rstrip("/\\") if not os.path.exists(new_path): self.write({"status": "error", "message": "路径不存在"}) return if not os.path.isdir(new_path): self.write({"status": "error", "message": "路径不是目录"}) return # 更新路径 RESOURCE_POOLS[pool_name]["path"] = new_path await scan_directory(pool_name) save_config() # 持久化 await WSHandler.send_updates() self.write({"status": "success", "message": f"资源池 {pool_name} 已更新"}) except Exception as e: logger.error(f"更新资源池失败: {e}") self.write({"status": "error", "message": str(e)}) class FileListHandler(tornado.web.RequestHandler): """ 显示某个资源池的文件列表页面 """ async def get(self, path): pool_name = path.strip('/') current_path = self.get_argument("dir", "").strip('/') if pool_name not in RESOURCE_POOLS: self.set_status(404) self.write("资源池未找到") return base_path = RESOURCE_POOLS[pool_name]["path"] if current_path: request_path = os.path.normpath(os.path.join(base_path, current_path)) else: request_path = base_path real_request_path = os.path.realpath(request_path) real_base_path = os.path.realpath(base_path) if not real_request_path.startswith(real_base_path): self.set_status(403) self.write("禁止访问") return if not os.path.exists(request_path) or not os.path.isdir(request_path): self.set_status(404) self.write("目录不存在") return files = get_file_list(pool_name, current_path) if isinstance(files, list): files = sorted(files, key=lambda x: (not x['is_dir'], x['name'].lower())) self.render( "file_list.html", pool_name=pool_name, files=files, current_path=current_path, pool_info=RESOURCE_POOLS[pool_name], len=len ) class FileDownloadHandler(tornado.web.RequestHandler): """ 文件下载处理器 """ async def get(self): pool_name = self.get_argument("pool") file_path = self.get_argument("file") if pool_name not in RESOURCE_POOLS: self.set_status(404) self.write("资源池未找到") return base_path = RESOURCE_POOLS[pool_name]["path"] full_path = os.path.normpath(os.path.join(base_path, file_path)) real_full_path = os.path.realpath(full_path) real_base_path = os.path.realpath(base_path) if not real_full_path.startswith(real_base_path): self.set_status(403) self.write("禁止访问") return if not os.path.exists(real_full_path) or os.path.isdir(real_full_path): self.set_status(404) self.write("文件未找到") return content_type, _ = mimetypes.guess_type(real_full_path) if not content_type: content_type = 'application/octet-stream' filename = os.path.basename(file_path) self.set_header("Content-Type", content_type) self.set_header("Content-Disposition", f'attachment; filename="{filename}"') self.set_header("Cache-Control", "no-cache") self.set_header("Pragma", "no-cache") try: with open(real_full_path, 'rb') as f: while True: chunk = f.read(64 * 1024) if not chunk: break self.write(chunk) await self.flush() await self.finish() except Exception as e: logger.error(f"下载失败: {e}") self.set_status(500) self.write("下载失败") class FilePreviewHandler(tornado.web.RequestHandler): """ 文件预览处理器(支持文本、图片、视频、音频、PDF) """ async def get(self): pool_name = self.get_argument("pool") file_path = self.get_argument("file") if pool_name not in RESOURCE_POOLS: self.set_status(404) self.write("资源池未找到") return base_path = RESOURCE_POOLS[pool_name]["path"] full_path = os.path.normpath(os.path.join(base_path, file_path)) real_full_path = os.path.realpath(full_path) real_base_path = os.path.realpath(base_path) if not real_full_path.startswith(real_base_path): self.set_status(403) self.write("禁止访问") return if not os.path.exists(real_full_path) or os.path.isdir(real_full_path): self.set_status(404) self.write("文件未找到") return content_type, _ = mimetypes.guess_type(real_full_path) if not content_type: content_type = 'application/octet-stream' ext = file_path.lower().split('.')[-1] if '.' in file_path else '' # 文本预览 if ext in ['txt', 'log', 'csv'] or content_type.startswith('text/'): try: with open(real_full_path, 'r', encoding='utf-8') as f: content = f.read() self.set_header("Content-Type", "text/html; charset=utf-8") html = f""" <!DOCTYPE html> <html><head><title>文本预览</title></head> <body style="font-family:monospace;background:#1a1a2e;color:#eee;padding:20px;"> <h3>{file_path}</h3> <pre style="white-space:pre-wrap;">{content}</pre> </body></html> """ self.write(html) except Exception as e: self.write(f"读取文本失败: {str(e)}") # PDF 预览 elif ext == 'pdf': self.redirect(f"/download?pool={pool_name}&file={file_path}") # 图片预览 elif content_type.startswith('image/'): self.set_header("Content-Type", content_type) with open(real_full_path, 'rb') as f: self.write(f.read()) await self.finish() # 视频预览 elif content_type.startswith('video/'): self.set_header("Content-Type", content_type) self.set_header("Accept-Ranges", "bytes") self.set_header("Cache-Control", "no-cache") try: file_size = os.path.getsize(real_full_path) range_header = self.request.headers.get("Range") if range_header: start, end = range_header.replace("bytes=", "").split("-") start = int(start) end = int(end) if end else file_size - 1 length = end - start + 1 self.set_status(206) self.set_header("Content-Range", f"bytes {start}-{end}/{file_size}") self.set_header("Content-Length", str(length)) with open(real_full_path, 'rb') as f: f.seek(start) while True: chunk = f.read(64 * 1024) if not chunk or f.tell() > end + 1: break self.write(chunk) await self.flush() else: self.set_header("Content-Length", str(file_size)) with open(real_full_path, 'rb') as f: while True: chunk = f.read(64 * 1024) if not chunk: break self.write(chunk) await self.flush() await self.finish() except Exception as e: logger.error(f"流式发送视频失败: {e}") self.set_status(500) self.write("播放失败") # 音频预览 elif content_type.startswith('audio/'): self.set_header("Content-Type", content_type) self.set_header("Accept-Ranges", "bytes") self.set_header("Cache-Control", "no-cache") try: file_size = os.path.getsize(real_full_path) range_header = self.request.headers.get("Range") if range_header: start, end = range_header.replace("bytes=", "").split("-") start = int(start) end = int(end) if end else file_size - 1 length = end - start + 1 self.set_status(206) self.set_header("Content-Range", f"bytes {start}-{end}/{file_size}") self.set_header("Content-Length", str(length)) with open(real_full_path, 'rb') as f: f.seek(start) while True: chunk = f.read(64 * 1024) if not chunk or f.tell() > end + 1: break self.write(chunk) await self.flush() else: self.set_header("Content-Length", str(file_size)) with open(real_full_path, 'rb') as f: while True: chunk = f.read(64 * 1024) if not chunk: break self.write(chunk) await self.flush() await self.finish() except Exception as e: logger.error(f"流式发送音频失败: {e}") self.set_status(500) self.write("播放失败") else: self.redirect(f"/download?pool={pool_name}&file={file_path}") class ManualScanHandler(tornado.web.RequestHandler): """ 手动触发扫描资源池 """ async def post(self): pool_name = self.get_argument("pool", None) if not pool_name: self.write({"status": "error", "message": "缺少 pool 参数"}) return if pool_name not in RESOURCE_POOLS: self.write({"status": "error", "message": "资源池不存在"}) return entries = await scan_directory(pool_name) await WSHandler.send_updates() self.write({"status": "success", "count": len(entries), "message": f"扫描完成,发现 {len(entries)} 个文件"}) class BatchDownloadHandler(tornado.web.RequestHandler): """ 批量下载多个文件为 ZIP 包 """ async def post(self): pool_name = self.get_argument("pool") if pool_name not in RESOURCE_POOLS: self.set_status(404) self.write("资源池不存在") return selected_files = self.get_arguments("files") # 获取多个文件路径 if not selected_files: self.set_status(400) self.write("未选择任何文件") return pool_path = RESOURCE_POOLS[pool_name]["path"] memory_zip = BytesIO() try: with zipfile.ZipFile(memory_zip, 'w', zipfile.ZIP_DEFLATED) as zf: for rel_path in selected_files: full_path = os.path.normpath(os.path.join(pool_path, rel_path)) real_request_path = os.path.realpath(full_path) real_pool_path = os.path.realpath(pool_path) # 安全校验 if not real_request_path.startswith(real_pool_path): continue if not os.path.exists(full_path) or os.path.isdir(full_path): continue arcname = os.path.basename(rel_path) zf.write(full_path, arcname=arcname) memory_zip.seek(0) self.set_header("Content-Type", "application/zip") self.set_header("Content-Disposition", f'attachment; filename="{pool_name}_files.zip"') self.write(memory_zip.getvalue()) await self.finish() except Exception as e: self.set_status(500) self.write(f"打包失败: {str(e)}")
最新发布
12-10
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值