Python少帅下飞机意味着什么

Python少帅下飞机意味着什么

这段代码到底干了啥

"少帅下飞机"是一段在网上传得很广的梗视频。这篇分享的,是一个用 Python 把视频实时转成 ASCII 字符画、并在全屏窗口里逐帧播放的小脚本——你扔进去一个 mp4,它就给你放一段字符版的"少帅下飞机"。

整个脚本不长,核心思路是这么几步:

  1. 逐帧读取视频:用 OpenCV 的 cv2.VideoCapture 把视频一帧帧抠出来。
  2. 缩放 + 灰度化:每一帧先按窗口宽度等比缩放(高度额外乘 0.55,是为了补偿等宽字符"瘦高"带来的比例失真),再转成灰度图。
  3. 像素映射字符:把每个像素的灰度值,按亮度映射到 "@%#*+=-:. " 这串字符上——越暗的像素用越"重"的字符,越亮的用越"轻"的字符,这样明暗关系就还原出来了。
  4. 渲染到窗口:把拼好的字符串塞进 tkinter 的 Label 逐帧刷新,字符画就动起来了。

几个值得注意的细节:

  • ImageEnhance.Contrast 把对比度拉到 1.5,字符画的明暗层次会更分明,不然容易糊成一片。
  • label.update_idletasks() + label.update() 手动刷新,而不是完全交给 mainloop,这样播放节奏能跟着视频帧走。
  • 播放完会自动 webbrowser.open 打开一个网页——这种"小彩蛋"按需删掉即可。

运行前先装好依赖 opencv-pythonpillow,把视频放进脚本同级的 Mores 文件夹,直接跑就行。完整代码如下:

代码如下

import cv2
import os
from PIL import Image, ImageEnhance
import tkinter as tk
import webbrowser

ASCII_CHARS = "@%#*+=-:. "

def resize_image(image, new_width):
    (original_width, original_height) = image.size
    aspect_ratio = original_height / original_width
    new_height = int(aspect_ratio * new_width * 0.55)
    if new_width <= 0 or new_height <= 0:
        print(f"Invalid dimensions: new_width={new_width}, new_height={new_height}")
        return image
    return image.resize((new_width, new_height))

def enhance_image(image):
    enhancer = ImageEnhance.Contrast(image)
    return enhancer.enhance(1.5)

def grayify(image):
    return image.convert("L")

def pixels_to_ascii(image):
    pixels = image.getdata()
    ascii_str = ""
    for pixel in pixels:
        index = pixel // (256 // len(ASCII_CHARS))
        if index >= len(ASCII_CHARS):
            index = len(ASCII_CHARS) - 1
        ascii_str += ASCII_CHARS[index]
    return ascii_str

def convert_frame_to_ascii(frame):
    if frame is None:
        print("Frame is None")
        return ""
    image = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
    new_width = root.winfo_width() // 6
    image = resize_image(image, new_width)
    if image is None:
        return ""
    image = enhance_image(image)
    image = grayify(image)
    ascii_str = pixels_to_ascii(image)
    img_width = image.width
    ascii_lines = [ascii_str[i:i + img_width] for i in range(0, len(ascii_str), img_width)]
    return "n".join(ascii_lines)

def update_frame(video_path, label):
    cap = cv2.VideoCapture(video_path)
    if not cap.isOpened():
        print(f"Error opening video file: {video_path}")
        return
    while True:
        ret, frame = cap.read()
        if not ret:
            print("No more frames to read or error reading frame.")
            break
        ascii_str = convert_frame_to_ascii(frame)
        label.config(text=ascii_str)
        label.update_idletasks()
        label.update()
    cap.release()
    label.master.quit()
    webbrowser.open("https://blog.biekanle.com/")

def play_video_as_ascii(video_path):
    global root
    root = tk.Tk()
    root.title("ASCII Video Player")
    root.attributes('-fullscreen', True)
    label = tk.Label(root, font=("Courier", 5), justify='left', bg='#282c34', fg='white')
    label.pack(fill=tk.BOTH, expand=True)
    logo_label = tk.Label(root, text="Mores", font=("Courier", 30), bg='#282c34', fg='white')
    logo_label.place(relx=0.99, rely=0.95, anchor='se')
    root.after(0, update_frame, video_path, label)
    root.mainloop()

if __name__ == "__main__":
    video_folder = "Mores"
    if not os.path.exists(video_folder):
        print(f"视频文件夹不存在: {video_folder}")
    else:
        for filename in os.listdir(video_folder):
            if filename.endswith(".mp4") or filename.endswith(".avi"):
                video_path = os.path.join(video_folder, filename)
                print(f"Playing {filename} as ASCII art...")
                play_video_as_ascii(video_path)
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。

1 条回复

A文章作者M管理员
上传评论图片
  1. Cmlessss
    Cmlessss学前班lv0
    想要