终端播放视频

文章目录

之前见过使用 telnet towel.blinkenlights.nl 命令在终端中播放《星球大战》。于是想自己也做一个。我选择的是《米奇妙妙屋》的片头。



我的思路是这样的:下载视频 -> 将视频提取成一张张图片 -> 将图片转化为像素画 -> 连续播放像素画 -> 放到服务器上使其他人也可以连接

视频切片

使用 FFmpeg 工具将视频切片。我选择将帧率定为 16,即每秒钟播放 16 张“图片”。如果帧率太高,终端会由于自身绘制速度及网络带宽导致刷新缓慢,进而导致视频看起来“很慢”。最后尝试时,Electerm 以及 Termux 的表现都很差,而 Windows 原生的 Shell(无论是 cmd 还是 Powershell)都有更优秀的表现。

使用 FFmpeg 视频切片的命令如下:

1sudo ffmpeg -i vid/vid.mp4 -vf fps=16 pic/frame_%04d.png
参数 解释
-i 输入文件
-v 设置视频限制

执行完上面的命令,我得到了 1354 张图片,这些图片名称依次为 frame_0001.png 到 frame_1354.png。

图像转字符

我使用 jp2a 工具将图片转化为字符。命令为:

1counter=1;
2for img in pic/frame_*.png; do
3  sudo jp2a --colors --color-depth=24 --height=77 "$img" --fill --chars=" ░" --output="txt/frame_$(printf '%04d' $counter).txt";
4  counter=$((counter + 1));
5done

这些命令将 pic/ 目录下的 frame_.png 图像转化成 txt/ 目录下的 frame_.txt 文本文件。

参数 解释
--colors 使用真彩色
--color-deepth 色彩深度
--height 生成的图像高度
--fill 使用填充
--chars 使用的字符
--output 输出文件

--height 生成的图像高度是指这张 ASCII 图像的行数,只有当终端的行数大于等于这个数值时,这张 ASCII 图像才能被正确显示出来。在 Linux 操作系统中,使用 tput lines 或者 echo $LINES 命令可以查看当前终端的行数。

--fill 是指填满行与行之间的空隙,实际上是给字符添加反色效果。在终端中,相邻两行字符之间有空隙,如果添加了反色 \033[7m 则会将空隙填充。

--chars 是指使用的字符。在这里我使用空格和“浅的阴影”(U+2591),这样两个字符在反色之后色块填充更饱满。

最后生成的文件使用 ANSI 转义序列调控颜色,如果直接使用 cat 命令打开,会看到正常显示的图像;使用 vim 命令编辑则会看到转义字符。

连续播放像素画

前面我知道了使用 cat 命令打开这些文件可以正常显示,那么我只需要依次 cat 这些图像就可以实现播放图像。

1for txt in txt/frame_*.txt; do
2  echo -e "\033[H";
3  cat $txt;
4done

输出的 \033[H 也是一个 ANSI 控制字符,用于将光标移动到终端的最开头。如果使用 clear 命令清空屏幕,则会出现屏幕频闪的效果,而将光标移动到终端开头则直接从上一帧图像上绘制,覆盖上一帧,避免屏幕频闪。

挂到服务器上

我在这一步遇到了一些问题。我最初的想法是将播放“视频”的脚本使用 netcat 上。例如我将上面播放的脚本存储为 TerminalVideo.sh,随后执行命令 nc -zvlp 6666 -e TerminalVideo.sh,这样其他机器执行 nc -zv 219.217.199.108 6666 命令就可以播放视频了。但是这样做也有诸多问题:

  • nc 一次只能建立一个连接,不能实现多用户同时连接
  • 在脚本运行结束后,nc 连接会自动断开

我想先尝试解决第二个问题,我运行下面的命令尝试持久化 nc 连接:

1while true; do
2  nc -zvlp 6666 -e TerminalVideo.sh
3done

但是这样又出现了新的问题,如果在脚本运行时,用户按下 ^C 强制退出,那么这条 nc 连接就会失效,服务端会持续报错 “Permission Denied”,客户端无法连接。

于是我又把目光放到了 SSH 上。之前在远程控制一台在内网中的设备里我尝试使用免密码的 SCP 传输文件。但是出于安全考虑,我需要将接受文件的用户的 Shell 改为一个空的 Shell,于是有了下面这段代码:

1void main()
2{
3  while (true) {;}
4}

最后测试证明,这段代码编译出来的程序是可以被用作 Shell 的。于是我现在需要做的是,写一个能够自动播放“视频”的程序,随后将其设为一个用户的默认 Shell,而且这个用户不应该有密码,使任何连接到校园网的用户都可以连接。首先我将 txt/ 目录移动到了 /etc 目录下(最开始选择的是 /tmp 目录,但是很快被系统自动清理了),并且下面一段代码:

 1// 6666.cpp
 2
 3#include <fcntl.h>
 4#include <sys/ioctl.h>
 5#include <unistd.h>
 6#include <iostream>
 7#include <fstream>
 8
 9
10using namespace std;
11
12int get_lines()  // 检测终端高度
13{
14    struct winsize ws;
15    int fd, result;
16    if ((fd = open("/dev/tty", O_WRONLY)) < 0) return -1;
17    result = ioctl(fd, TIOCGWINSZ, &ws);
18    close(fd);
19    return result ? -1 : ws.ws_row;
20}
21
22int main()
23{
24    int rows = get_lines();
25    if (rows == -1)
26    {
27        cout << endl;
28        cout << "\033[7;31mUnknown Error!\033[0m" << endl;
29        cout << endl;
30        return -1;
31    }
32
33    else if (rows < 80)
34    {
35        cout << endl;
36        cout << "+---------------------------------------------------------------+" << endl;
37        cout << "| The minimum height of your terminal to play this video is \033[7;31m80\033[0m. |" << endl;
38        cout << "| Use \033[7;33mtput lines\033[0m to check your terminal height.                 |" << endl;
39        cout << "+---------------------------------------------------------------+" << endl;
40        cout << endl;
41        return -1;
42    }
43
44    for (int i = 1; i <= 1354; i++)
45    {
46        cout << "\033[H";
47        char filename[24];
48        sprintf(filename, "/etc/txt/frame_%04d.txt", i);
49
50        fstream file;
51        file.open(filename, ios::in);
52        string line;
53        while (getline(file, line))
54        {
55                cout << line << endl;
56        }
57    }
58    return 0;
59}

多种编程语言都对 ANSI 控制有支持,C++ 也不例外,因此我直接 cout 控制字符,控制字符就能正确执行其功能。

下一步就是建立一个无需密码就可以登录的用户,并将其默认 Shell 设为我们刚刚写的程序。需要执行以下命令:

1sudo adduser mickey
2sudo passwd -d mickey

随后修改 /etc/ssh/sshd_config 文件,修改其中配置 PermitEmptyPasswords yes,随后重启 SSH 服务 sudo systemctl restart sshd 使修改生效。

下一步使用命令 sudo chsh -s /bin/6666 mickey 修改默认 Shell。

现在来看,功能已经大体实现。但是还有一个问题,在连接上的时候,服务器会输出 MOTD(Message of the Day),然而这些信息不应该对任何人可见,因此在 mickey 用户的家目录下创建 .hushlogin 文件 sudo touch /home/mickey/.hushlogin 以禁用 MOTD。

最终效果如下:



(没有声音)