0%

前言

大概两年前,vscode推出了以SSH为基础的远程开发插件,当时还只是略作了解,然后拿来作为远程文本编辑的一个备选项。相当于是ssh环境下vimemcas的一个替代品。作为GUI基础的文本编辑器,在访问未经配置远程环境下,相比使用全默认配置的vim还是要更好用一点,不过也就仅限于此了。

最近翻出来研究的主要原因,是自己的办公电脑配置慢慢跟不上开发需要。虽然4c16g的配置不算低,不过办公机需要开启大量的网页窗口、各类文档、IM软件等等,再加上IDE和大量依赖服务,而且本身所处的开发环境还需要运行额外的监控软件、防病毒软件等等,实际在开发过程中和代码编辑时还是能明显感受到拖慢的。测试一个完整的java项目编译,server端2分钟可以跑完,本地要跑上十几分钟,确实浪费了不少生命。于是一不做二不休,捣腾一下全给搬到server上,通过远程访问方式进行开发。

总结一下的话,远程开发带来的好处大概有这么几个

  • 更可控的机器配置,云端虚拟机可以比较方便的做在线扩容。目前我使用的配置是8c32g,如果有需要完全可以扩到32c256g甚至更高。
  • 便利的环境快照。基于虚拟机的开发环境,折腾前先做个快照,发现什么问题可以随时回退。或者机器更换,基于快照恢复也很方便。
  • 纯原生的linux环境。没啥好说的,可以完整使用linux的开发生态。使用docker镜像啥的也比较方便
  • 上下文环境保持。因为server端基本可以一直挂着不用关,不用烦恼每次重启后又要打开一大堆调试窗口或者是启动一堆背景服务。

另外还有一些可以算作远程开发的优势,不过在我的使用场景下不涉及的

  • 对客户端性能要求较低,具有一定移动开发友好度。比如在阿里云上申请台高配server,然后本地使用mba或者surface开发,也基本不会受限于cpu或内存等硬件桎梏。
  • 低成本的多设备切换。在公司开发到一半了,出差或回家了,用笔记本远程连上就可以继续工作。当然这点要看具体的网络环境了。

至于缺点也有一些,不过我觉得核心就是一条,就是vscode作为一个主打文本编辑器的轻量级开发环境,能否满足开发效率上的需求。

我个人感觉还是有点难度,当然其中也有一些使用习惯上的问题。比如对于版本管理,我个人更习惯intellij中提供的version control界面。基于命令行的tig或是vscode 的插件git lens,个人习惯上还是觉得差一点。

另外对于java项目来说,idea的代码补全(code complete)还是要比vscode更好一点。虽然现在新一代的基于AI模型的代码补全工具在很多时候确实也非常好用,不过短期内感觉还很难取代传统基于语言模型的代码补全。当然最致命的一点是,由于java ee项目的技术栈深度问题,诸如mavenspring bootjunitlombok等等,idea已经提供了很完备的插件生态来支持开发,而vscode在这方面则多还处于起步阶段。

总体而言,给人的感觉是,在使用IDE时,通常开发者不需要对其背后的运作原理有太深入的理解,只需要专注在自己的代码上就好了。而基于vscode的开发环境,则需要开发者对代码的编译、调试、打包等流程都需要有一定了解才能顺利进行。

如果上面说的这些都不成问题,那么搭建一个server端的开发环境还是非常实用的。

环境搭建

前置准备

这里要准备的东西倒不是整个开发环境必备的,倒不如说是作为开发环境,我个人习惯会准备的一些开发工具。

  1. zsh&oh my zsh

    习惯使用zsh,自然是第一时间会装上

  2. tmux

    基本是server环境下必装的,用于会话管理和终端窗口管理,也是减轻vscode本身的terminal在窗口管理上功能孱弱带来的影响。

  3. docker

  4. pet

    因为windows端我没有用类似dash这样的工具管理命令行snippet,因此选择在linux环境装了pet用来做日常命令的收集。

  5. tig

    虽然vscode本身也有git插件,不过纯粹的命令行交互式的git工具在很多时候还是很方便的。

  6. Ncat

    比较通用的网络工具,在远程环境下我拿来作为一个比较简单粗暴的转发工具

  7. Ant、maven、gradle、java、gcc、python等开发工具

说实话,如果有人能把这些开发环境统统装好打成镜像,我觉得还挺有用的。不过就算自己准备也不算太费事吧,linux环境下通过包管理工具通常都可以很便利的装好。(我使用的server环境是rhel7.4,因此使用的是yum进行包管理)。

vscode remote安装

本身remote插件的安装其实很简单,在vscode中打开extension然后搜索Remote SSH就能装上。需要额外一提的是,本身这个插件是作用于本地的vscode的。对于server端而言,还需要单独安装vscode-server。当然如果是处于互联网环境的话,那么第一次使用vscode连接到远程服务器的时候,就会自动去下载server端安装包。

不过如果你的server是处于非互联网环境下,那么注意这个安装过程虽然不能顺利进行,但是可以通过提前下载部署server包来跳过,具体可以参考这个帖子。**另外需要注意的是,vscode-server版本是要与vscode匹配的,因此如果vscode更新过了,那么也需要注意更新vscode-server**。

除此之外,基于remote模式,vscode只有界面渲染等与UI相关的部分是在客户端执行的,编辑器内的计算部分是在服务端完成。因此,想要使用vscode的插件,需要在server端安装才能正常使用。

除了基础的编辑、保存等功能外,remote模式提供了一个非常实用的功能就是端口映射。这是由于,因为编辑的代码都是实际存储在server端,编译和调试自然是在server端进行。那么比如我在开发一个前端项目时,写完代码想在界面上调试看看效果,那么自然是要npm start把网站跑起来。可是此时网站是启动在server上,虽然应用会提示说请通过localhost:xxxx来访问,但实际在本地自然是访问不到的。remote插件提供的端口映射功能,就是通过侦测远程端口的开启情况,将本地对应的端口映射到远程端口上,来达到本地访问远端服务,实际体验上就和在本地调试一样。

另外就是我在上面提到的装的ncat。这是因为某些代码需要访问本地客户端的一些服务,距离来说,由于某些原因,我没有在server端安装mysql数据库,此时server端访问本地的mysql就会访问不到。于是我干脆通过ncat --sh-exec "ncat <ip> <port>" -l <port> --keep-open把server上访问mysql的请求转发到本地。这当然不是必须的,不过在过渡期懒得折腾的时候倒也能省不少事。

IDEA&Rsync

虽然理论上采用这种方式,就可以把代码全部搬到server端进行开发。不过正如上面我提到的,使用vscode进行开发,在java ee这种特定场合下,效率上还是不如使用ide来的更方便快捷。于是我想到一个折中妥协的法子,就是在本地用ide开发后,将代码同步到服务端进行编译调试。实际体验中,采用rsync进行同步,其效率还是很高的。

对于单纯的代码同步,rsync命令如下

rsync -avz --filter=':- .gitignore' --delete -e "<ssh_path>" -r <source_dir> <user>@<server_id>:<target_dir>

除了第一次代码的全量同步以外,rsync在后续的同步中会自行进行增量对比,整体效率还是相当不错的。使用.gitignore文件则是把编译、构建过程中不必要的文件排除掉,某种程度上也能加快同步效率。

后记

这通折腾我大概在3月中旬的时候折腾完毕,把整套开发环境顺利搬到了服务器上,也顺利用了近一个月。

意料之外的是,想不到就在我折腾完没过两周,jetbrains就发布了projector产品,同样是通过远程渲染的方案,将基于swing的IDE工具搬到server上,而本地只需要通过浏览器或瘦客户端来访问。

可能未来的趋势,就是IDE慢慢云端化吧。我个人倒是还挺待见这种趋势的,有空的话再折腾一下部署projector,若是可以把idea也搬到服务器上,本地开发就可以彻底成为过去式了。

最近碰到一个蛮有意思的问题. 我原来在windows平台下有使用一款叫timesnapper的软件. 这个软件的作用是可以在间隔一段固定的时间后做一次屏幕截图, 将图片保存下来. 我用这个功能主要是用来做时间开销的记录. 此外, 之前在hackernews上看到过一个有意思的帖子是对比20年来的桌面的变化. 所以我觉得定期记录一下自己的电脑也算是蛮有意思的一件事情.

不过在我换到mac平台的时候, 没有找到一款类似的软件. 于是我写了一个很简单的定时脚本, 每隔几分钟调用一下macos自带的截屏工具保存图片. 这里的代码很简单, 大致如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/local/env python
#coding=utf8
import datetime
import subprocess
import os


def main():
path = '/Users/xdsoar/Documents/ScreenRecord/'
time = str(datetime.datetime.now())
date = time[0:10]
month = date[0:7]
path = path + month + '/'
newPath = path + date + '/'
if not os.path.exists(path):
os.mkdir(path)
if not os.path.exists(newPath):
os.mkdir(newPath)
time = time.replace(' ', '_').replace(':','.')
file1 = newPath + time + "_1.png"
file2 = newPath + time + "_2.png"
file3 = newPath + time + "_3.png"
param = "screencapture -x " + file1 + " " + file2 + " " + file3
print("calling the cmd" + param)
subprocess.call(['/usr/sbin/screencapture', '-x', file1, file2, file3])

if __name__ == '__main__':
main()

写完以后放了一个定时任务在crontab里就算完工了. 这几年来一直运转正常. 不过有一个小毛病过去倒是一直困扰着我. pc上的timesnapper有一项功能就是检测到画面没有变化时, 就不会截屏. 我猜测原理可能是识别如键盘、鼠标的活动时间, 或者干脆就是识别画面. 这个功能可以节省非常多的冗余图片. 而我自行写的这个截图小程序自然是没有这个功能的.

于是这个周末稍微花了点时间研究了一下图像查重的方法. 很容易查到了一个叫做ssim的算法, 用于判断图片之间的相似度. 这里的相似度是基于结构相似度, 虽说可能和我的场景不是完全一致, 不过实际测试过以后发现还是比较符合预期的. 基于方便的原则, 还是用python写了一个, 基于opencv的ssim实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import os
from skimage.measure import compare_ssim
import cv2
import cProfile

def get_file_list(path):
files = os.listdir(path)
gray_files = []
file_compare_result = []
times = 0
for file in files:
image_file = cv2.imread(path+'/'+file)
gray_image = cv2.cvtColor(image_file, cv2.COLOR_BGR2GRAY)
is_dedup = False
for i in range(0, len(gray_files)):
gray_file = gray_files[i]
if gray_file.shape != gray_image.shape:
continue
(score, _) = compare_ssim(gray_image, gray_file, full=True)
if score > 0.99:
gray_files.pop(i)
gray_files.insert(0, gray_file)
is_dedup = True
break
file_compare_result.append((file, is_dedup))
if len(gray_files) > 10:
gray_files.pop()
gray_files.insert(0, gray_image)
print('done once ' + str(times))
times += 1
return file_compare_result



if __name__ == "__main__":
cProfile.run('result = get_file_list("/Users/xdsoar/Documents/project/image-dedup/sample")')
print(result)

这段代码里用了cProfile, 是因为写完以后对性能有点好奇, 不知道是花在图像加载上, 还是实际的ssim算法上. 跑了一下发现基本都是花在图像加载上. 因此代码里对于图片的比较稍微做了点优化. 虽然说只比较前后两张图片的话, 时间复杂度就会很美好了. 不过实际上会遇到一种情况就是电脑锁屏以后的待机画面经常被不连续的截进来. 因此还是希望尽可能放大对比的范围. 当然如此一来, 时间复杂度就可能上升到o(n²). 对于本身就比较耗时的ssim算法而言, 时间开销就会相当庞大.

顺带一提, 我原来在mac上运行这个代码的时候, 基本上cpu都被吃满了, 因此我推测ssim算法是可以用到多线程的. 于是我尝试同样的代码和文件搬到了台式机上, 因为台式机用的是8核cpu而笔记本2核, 预期是可以有数倍的性能提升的. 结果台式机上运行反而慢过笔记本, 而且cpu并没有吃满, 只使用了10%左右. 这里我的猜测是笔记本上的opencv可能是使用了pyopencl, 用核显做了加速. 而台式机因为显卡是nvidia的, 可能并不支持加速.

于是我开始考虑如果能使用nvidia的显卡(我用的是1060)来做计算的话, 或许还能快一些. 于是开始找支持n卡加速的ssim算法库. 看了一圈发现pytorch支持n卡的cuda框架, 并且也有ssim算法的实现. 于是几经波折在pc上装上了pytorch. 代码也改成了如下版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import os
from pytorch_msssim import ssim, ms_ssim
import torch
import numpy as np
from PIL import Image
import shutil


def get_file_list(path):
files = os.listdir(path)
gray_files = []
file_compare_result = []
times = 0
for file in files:
if not (os.path.isfile(path+file)):
continue
image_file = Image.open(path + file)
size = image_file.size
resize_image = image_file.resize((int(size[0]/2), int(size[1]/2)),Image.ANTIALIAS)
image_array = np.array(resize_image).astype(np.float32)
image_torch = torch.from_numpy(image_array).unsqueeze(0).permute(0, 3, 1, 2) # 1, C, H, W
image_torch = image_torch.cuda()
is_dedup = False
for i in range(0, len(gray_files)):
gray_file = gray_files[i]
if gray_file.shape != image_torch.shape:
continue
score = ssim(image_torch, gray_file)
if score.item() > 0.99:
gray_files.pop(i)
gray_files.insert(0, gray_file)
is_dedup = True
file_compare_result.append((file, is_dedup))
if len(gray_files) > 100:
gray_files.pop()
gray_files.insert(0, image_torch)
print('done once with ' + str(times))
times += 1
return file_compare_result


def compare(file1, file2):
ima1 = to_cuda(Image.open(file1))
ima2 = to_cuda(Image.open(file2))
score = ssim(ima1, ima2)
print(score.item())

def to_cuda(ima):
image_array = np.array(ima).astype(np.float32)
image_torch = torch.from_numpy(image_array).unsqueeze(0).permute(0, 3, 1, 2) # 1, C, H, W
return image_torch.cuda()


if __name__ == "__main__":
path_arg = "C:\\Users\\qwerp\\Documents\\source\\dedup\\sample\\"
results = get_file_list(path_arg)
os.mkdir(path_arg + 'dedup')
for result in results:
if result[1]:
shutil.move(path_arg+result[0], path_arg+'dedup/'+result[0])
print(results)

结果是速度上确实有了非常感人的提升, 体感应该有5到10倍吧. 以后如果换用更好的显卡, 应该会有更好的效果. 另外在做优化的时候小开了一个脑洞, 用PIL将图像长宽都缩小到原来的一半, 结果不仅在计算速度上有非常大的提升, 而且显存能存储的图片数量也是有了一个量级的提高. 是的, 这里依旧限制了只保存100张图片进行对比, 原因就是将大量图片转换成向量以后保存在显存里还蛮大的, 未缩减大概是单张80mb. 最后的对比结果也基本符合预期, 重复的图片都找了出来. 只是这里阈值设置低一点的话(比如95), 那么一些稍微有点区别的截图也会被抓进来. 很难说哪样取舍更好. 我想最终我会使用的方案是删掉那些完全重复(重合度超过99%)的截图释放空间, 而把那些高重复度的截图单独建目录存放.

做完以后我又想到一个点子. 同样用ssim算法, 不知道能不能找出所有截图里最另类的一批? 因为图片存着以后我还是会定期回顾的. 但是大量图片的回顾确实也非常累, 如果能从大量图片中找到相对高价值的那些, 应该能节省不少精力. 关于这一点, 接下来有时间我想我会琢磨琢磨.

发布地址, 以及github的链接.

本身是在这个月初的时候做的东西, 初版发布的时候还在nga发了帖子. 起因是dnf新的100级版本是个深渊版本, 想大致算一下需要投入的时间.

周末某天早上的时候突发奇想, 用python写了点代码用蒙特卡洛来算概率. 算了几组以后发觉对界面操作的需求还挺大的, 于是决定gui重构一版. 因为要画界面, 因此也就不想用python了, 于是盯上了vue. 本来是打算用vue+electron做一个跨平台的客户端版本的. electron也一直是我想了解的一项技术, 用vue只是单纯觉得更轻量一点, 上手可能可以快一点.

最后整套技术栈用的就是vue+typescript+electron(可有可无). 实际开发中基本感知不到electron, 包括调试和发布最后其实也只是基于传统web的形式. 开始趟了点小坑, 用的electron-vue的模板, 结果这玩意连启动hello world都会报错……后面改用vue-clielectron-builder, 总算是顺利多了. 改typescript稍微花了点时间, 主要是vue本身的教程都是基于js的, 用ts的思路来理解的话多少有点不顺, 这篇博客看看还是蛮有收获的.

最后发布用了和博客类似的基础设施. 只不过之前博客用的是hexo的s3插件, 这次是更通用的npm插件, 以后如果有类似实验性质的项目, 应该都可以用类似的方式来发布. 我特意做了这个lab的二级域名, 也是希望以后能再往里面填一些东西. 因为原来用的就是s3, 所以这次发布也还是在s3, 然后用cloudfront做加速, 国内访问确实算不上非常快, 后面有机会再看是不是迁到阿里云之类国内的服务商吧. 本次部署的时候遇到了一些坑, 基本都是我在这篇博文里提到的, 想不到完全适用, 还是挺意外的.

发布以后我想着针对像多选, 更复杂的计算条件做了一些优化. 不过本身这个应用的使用场景还是蛮有限的, 后续是否继续更新, 就看心情了…..不过作为vue的上手项目, 还算不错.

我一直习惯用笔记本外接dock的用法,特别是最近几代的笔记本(不管是pc还是mac)配备了thunderbolt3接口以后。tb3接口的高带宽,实现了单一接口外接多个高分辨率显示器,使用体验上感觉还是非常好的。像是我带笔记本在公司里用。只要在工位上放置一个tb3的扩展坞,使用的时候只要连接一根tb3数据线,就能满足电脑充电、外接显示器等功能。外设的连接,像鼠标和键盘当然也是可以的。不过因为这样一来桌面的线材会非常多,所以键鼠一类低速设备我还是倾向于使用无线的。

不过这种使用方式在我的实际使用中存在一个不大不小的痒点。就是在笔记本连接了扩展坞以后,显示器并不能自动的从台式机切换到笔记本上。而且自动切换也不现实,因为设备当然不知道我什么时候想用笔记本,什么时候想用台式机。在只有单个屏幕的时候,这件事还不算很麻烦,我只要通过显示的OSD菜单切换输入源就可以了。但是当我连接了多个显示器以后,这个问题就愈加麻烦。直到某次在论坛偶然看到有人提起DDC协议,我才知道这个问题是可以解决的。

简而言之,显示器的数字接口(如DisplayPort和HDMI),是可以双向通信的,因此从主机向显示器发送命令也就是可行的。DDC协议也就因此应运而生。像笔记本上的功能键控制屏幕亮度等功能,其实本质上也是通过这个协议完成。知道了协议以后我就去找了找是否有现成的软件。还算顺利的找到了。

Windows下我使用的是一款别人推荐的免费软件ClickMonitorDDC,有图形界面,也有命令行接口。虽然CLI命令和常用的UNIX系命令不太像,不过使用上还算比较简单。以我用的LG显示器为例,要将输入源切换成HDMI,可以用命令ClickMonitorDDC_7_0.exe s LGUltraHD HDMI1。显示器的命令可以事先通过图形界面的控制面板拿到。最后我选择的用法是将切换命令存到我的AHK脚步里,实现一键切换。

Mac下则是搜索到了github上的一个开源项目,ddcctl。需要自行下载源码编译安装。编译安装好了以后,就可以通过如ddcctl -d 1 -i 15的命令,将指定显示器切换成指定的输入源。对比windows平台的软件,有一个小的不足是显示器是通过编号识别的。但是我实测通过tb3连接外接显示器时,每个显示器的编号并不固定,想要准确切换还需要自己写脚步识别每个编号的显示器具体是什么。当然如果每个显示器的连接方式都一样,那就没有这个烦恼了。Mac上的快捷工具我习惯用Alfred,因此就把相关的命令写成Alfred workflow来用就可以了。

一些小坑

  1. Dell的显示器(以我用的U2312为例)只能接收当前输入源的主机输入的命令。举例来说,我的台式机通过HDMI连接显示器,笔记本通过DP连接。当显示器输入源为HDMI时,只有台式机发送的DDC命令才会被执行。反之,当输入源为DP时,只有笔记本发送的命令会被执行。
  2. 几年前买的EIZO显示器,不支持DDC命令,那自然是没有办法控制了,比较遗憾。我觉得以后我会把这一条放在购买的要求里。如果不支持DDC,可能就不会考虑购买了。
  3. Mac我有两个dock,在家里用的是thinkpad的tb3 dock,公司里是belkin的tb3 dock。其中通过thinkpad的dock连接显示,ddcctl会提示发送命令失败,而belkin的则没有问题。本身显示器端的问题我也已经排除了,是支持ddc的。看来应该是dock本身对ddc支持的问题。本来买thinkpad的dock是出于兼顾window和mac的考虑。如此看来,以后还是要针对专项的笔记本选购最适用的dock才行。如果下一台笔记本还是用mac的话,就要考虑把家里的dock换掉了。

前阵子游戏上录了几个视频, 传到b站, 顺便学了点视频编辑和压制的知识. 主要是x264ffmpeg相关的. 不过浅尝辄止, 这部分就先不展开说了.

由于b站最高能支持1080p的视频上传, 而录的游戏视频原始分辨率是720p的. 720p一方面本身分辨率不如1080p, 另一方面对码率的限制更严.(1080p能上传6k码率的视频, 720p最高只能上传3k码率). 根据我压视频的经验, 码率上升带来的画质提升是大于分辨率上升带来的负担的. 因此有条件当然是传1080p的更好.

这就涉及到视频upscale的问题. 几年前用过waifu2x做一些图片的放大, 效果还不错, 一些比较老的低分辨率图片放大到高分辨率后可以拿来做现在显示器或者手机的壁纸. 不过视频的upscale我推测和单纯图片的放大还是不太一样, 因此还是趟了点坑.

拿自己录的原始720p视频作为素材, 第一步是拆帧. 因为waifu2x本身只支持图片放大, 所以需要把视频先逐帧拆成图片. 可以用ffmpeg拆, 也可以用adobe premire. 如果视频需要先编辑的话, 建议就直接用pr编辑完导出图片序列了. 纯拆帧用ffmpeg也很快, 命令格式如下ffmpeg -i <video_file_name> <image_name>_%05d.png. %05d是指定图片序列的名字, 如果视频很长的话, 也可以将位数扩长. 当然图片太多的话, 本身用waifu2x也会有性能问题.

接着就是导进waifu2x里面跑了. 因为之前我下载的是带gui的版本, 直接选择图片所在目录, 指定渲染的参数就好了. 作为参考, 我用的放大2倍, 开3级降噪, 对720p图片做放大, 速度大约是1秒1张. 2分多钟60帧的视频, waifu2x渲染完大约用了1小时50分钟, 显卡是gtx1060 6g版本, 应该说还是挺久的.

最后在pr里导入图片序列再合成视频即可. 因为是图片合成, 声音自然是没有的. 将原先视频的声音合并进来就好了, 这一步不管用pr还是ffmpeg都很快. 最后再导出视频, 这一步又会花不少时间. pr的h264实现据说效果不如x264. 但是无损格式导出实在太大了. 暂时没有配置帧服务器, 所以我是先用pr导出码率较高的版本, 再用x264压制一遍上传的版本. 两步各会花费视频时间2~5倍的压制时间. 因为视频不长, 所以还算可以接受.

渲染完的视频可以参考这个链接.waifu2x渲染测试视频

一些坑

waifu2x渲染本身对于图片质量有一定要求, 如果图片很糊的话, 渲染效果会比较差, 因此就对视频源的质量有比较高的要求. 哪怕是本来实际观感还可以的视频, 如果本身码率不足, 拆完的图片有一些细节丢失的话, 对渲染的画面会有比较大的影响. 也就是说, 对于低分辨率低码率视频->低码率图片->waifu2x渲染->高码率高分辨率图片->高码率高分辨率视频, 最终出来的高分辨率视频, 观感不会很好.

实际测试下来, 比较适用的场景是低分辨率高码率视频->高码率图片->waifu2x渲染->高码率高分辨率图片->高分辨率高码率视频->高分辨率低码率视频. 这种路径是是适用的, 最终压制完上传到视频网站的视频可以保持如6k这种方便网络传输的码率. 但是原始视频需要的可能是高一个数量级的码率(比如这里我的测试视频使用了60k的码率).

另外一个有意思的现象是, waifu2x对图片做降噪的时候, 会有明显的画风变化, 产生类似”油画感”. 这种风格对于动画风格的图片, 不得不说还蛮受用的, 在很多场合下效果都不错.

不过这种变化随着图片分辨率的上升会呈下降趋势, 对于720p的图片, 降噪后”油画感”很明显, 升上到900p, 乃至1080p后, “油画感”逐渐消失. 对同一张图片, 原始分辨率是1440p, 逐步下降下720p, 对比比较明显. 参考下图. 第一张是原图, 第二张是经过waifu2x渲染后的. 因为上传博客原因, 尺寸统一缩到了720p, 不过画风变化还是可以直观看出来.

frame_09196

frame_091962

个人猜想是因为随着分辨率上升, waifu2x在渲染时能补充的细节变少, 因此这种画风变化也逐渐变少. 如果比较想要这种滤镜效果, 且原始图片(视频)分辨率较高, 那么还要做一次downscale. 这方面也有一些不错的算法, 不过我暂时也没空折腾这些, 就不展开讲了.

依旧是上次的python 项目, 周末抽时间把流水线建了, 顺便加了一个badge. python项目的流水线相对简单, 对于编译流水线来说, 其实只要跑一下测试就好了.

这里我用的测试框架是pytest, 以azure流水线为例, yaml内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
trigger:
- master
- dev
- azure

pool:
vmImage: 'ubuntu-latest'
strategy:
matrix:
Python36:
python.version: '3.6'
Python37:
python.version: '3.7'

steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(python.version)'
displayName: 'Use Python $(python.version)'

- script: |
python -m pip install --upgrade pip
cd taskcommander
pip install .
displayName: 'Install dependencies'

- script: |
pip install pytest-azurepipelines
pip install taskcommander/.[test]
pytest --cov=./taskcommander/
displayName: 'pytest'

- script: |
pip install codecov && codecov -t $(CODECOV_TOKEN)
displayName: 'upload code coverage'

azure对于python项目支持多版本测试, 写一套流水线用多个python版本运行, 如上面的yaml中就写了3.6和3.7两个版本, 2.7因为一开始就没想着支持, 3.5因为对于type hint的支持不太一样, 因此这两个版本就去掉了.

步骤是非常简单的三步

  1. pip install安装python包(其实不需要发布的话这步骤也可以去掉, 不过我觉得安装也算是一个测试环节吧)
  2. pip install安装pytest以及测试需要的包, 并运行测试
  3. 将代码覆盖率(code coverage)上传到codecov.

每个步骤都很简单, 就不展开讲了. 唯一需要注意的是上传codecov中用了一个环境变量. 这是因为流水线的配置yaml一般是写到代码库(github)上的, 一般不推荐把诸如用户/密码等敏感信息写到代码库里去, 因此用环境变量的做法更合适一些. 而且azure pipeline支持定义密码类环境变量, 对于此类变量, 在pipeline的输出日志里也会隐藏, 提供更好的保密性.

配置好以后, 每次提交代码到masterdev分支就会自动触发流水线运行, 如果运行报错, 也会有邮件提醒. 另外, 配置了codecov以后, 创建pr时也会自动提醒合并代码会带来的覆盖率变化.

最后是给项目readme加上codecov的badge, 个人感觉算是展示项目一个比较实用的badge. 在readme里加入类似以下内容, 就能正常展示badge了.

1
[![codecov](https://codecov.io/gh/xdsoar/TaskCommander/branch/dev/graph/badge.svg)](https://codecov.io/gh/xdsoar/TaskCommander)

每个项目的badge可以在登录codecov后, 在项目的settings->badge中找到, 不需要自己敲.

GTD, 或者说todo类的产品, 是我一直有频繁使用的一类应用. 不管是哪个平台, 从桌面系统windows\mac到移动端ios\android, 我都有不少尝试. 其中不乏佼佼者, 遗憾的是总是因为跨平台的一些原因, 在这个平台上好用的应用, 换了平台要么不好用了要么就是根本没有.(比如仅限mac系统的omnifocus, 仅限windows系统的mlo).

于是这次筹划干脆自己写一个吧. 初期打算先写一个cli版本的, 有时间了会再研究用诸如electron来写一个跨平台gui版本. 当然移动端依旧是个问题, 这个问题就交给以后去烦恼了.

这个应用的架构会按照这一年里实践了很多次的洋葱架构来写. 关于洋葱架构, 以后有时间会具体来写.

这次主要搞定的是python项目发布的问题. 既然是做成cli应用, 那么能用pip安装自然是最好的.

python的项目结构参考了https://python-packaging.readthedocs.io/en/latest/minimal.html这里的说明. 最小化发布至少需要有一个setup.py文件, 其中包含如发布的名字, 版本号, 依赖等信息. 这里的依赖配置还没研究透, 原来的习惯是写一个requirement.txt文件, 然后在部署的时候pip install, 不过这种做法对于发布成pip包的应用大概不是很友好.

发布的环节与上面网页上描述的略有不同. 因为在使用sdist upload时看到提示建议使用twine来做upload. 因此发布前先pip install twine, 然后使用twine uploa命令发布制品. 发布前需要先去pypi网站上注册账号, 然后才能发布.

我从去年回来玩DNF开始, 就经常录制视频记录自己的游戏经历. 特别是在换了电脑, 录制视频的性能开销基本影响不到游戏体验以后.

途中也更换了好几次录制软件, 因此记录一下工具链更迭的经历.

最初的选择 windows game bar

最开始我选用的是最简单的方案. win10系统自带的游戏录制功能. 这个功能本身还是比较良心的, 特别是在系统更新到1903以后, game bar有了非常丰富的功能. 不过仅针对游戏录制这一点, 不得不说还是有所不足.

首先是录制的码率可选的范围比较有限, 通常需要录完以后再自己用x264之类重新压一遍. 多次压缩对于视频质量也是一点折损. 此外对于音频也没有太多的选项, 反正有声音就录下来了.

另外对于我录DNF来说, 还有一个额外的不足是它只能录制单个游戏窗口. 因此当我使用游戏的外置聊天窗口时, 聊天窗口是无法被录进来的.

这个方案我一直用到今年三四月份. 直到我开始为游戏竞速比赛录制视频时, 才逐渐感觉到工具的不足, 遂开始找其他替换方案.

更强的NVIDIA experience

因为电脑配的显卡是N卡, 因此我很自然的开始用N卡自带的游戏工具来尝试录屏. NVIDIA experience基本延续了windows game bar的体验, 提供的选项非常有限. 不过功能上还是要强一些.

首先他提供了全屏录制的功能, 因此我不用再为聊天窗口无法录制烦恼. 其次是提供了非常丰富的视频码率范围选择, 可以比较容易兼顾画质和文件尺寸.此外还可以为麦克风设置独立的音轨, 可以把游戏声音和语音区分出来.

不过NE的功能虽然更强, 但依然有几个美中不足的点. 其一是虽然麦克风的声音可以被单独分到一个音轨上, 但是语音中听到的声音, 依旧和游戏(或者说整个电脑)的声音混杂在一起. 我有时会把录完的视频上传到视频网站, 但包含语音的话就不太好, 一般来说这时候我希望上传的视频只包含游戏本身的声音. 我在网上搜到过一个方案, 能将语音聊天双方的声音按左右声道放到一个音轨中, 但我尝试后发现还是没办法把声音从游戏声音的那条音轨中去掉.

另外一个点是由于全屏录制的缘故, 所以视频录制的对象是屏幕, 而不是窗口, 当我的游戏画面被别的窗口(比如网页或者聊天窗口)覆盖的时候, 这一情况也会反应在我录制的视频里.

此外因为录制是全屏的关系, 但实际游戏窗口大概只是整个屏幕的3/4, 因此录制完, 如果需要上传到视频网站的话, 我还需要用pr对视频做一定的裁剪, 使得视频的整个编辑流程变的很长.

最终选择 OBS

最后我在上个月开始使用OBS, 也就是我现在使用的方案. OBS是一款有一点历史的软件, 我在很久以前也用过. 也正是因此, 我对当时的OBS录制的视频画质有一点反感.(因为本质上OBS是一款同时面向视频和直播的软件, 相对画质, 会更注重录制时效性和性能).

不过可能是因为与NVIDIA合作的关系, OBS在今年起支持了NVIDIA的编码器NVENC, 使得录制的画质有了很大的提升. 我在实测以后也觉得画质完全没有问题, 甚至相比NVIDIA experience还提供了更低码率的选项, 同时画质也还不错.

此外OBS支持多窗口录制, 因此我终于可以以窗口的形式录制游戏本体和聊天窗口, 并且不用担心游戏被其他窗口覆盖.

多音轨也有了更好的支持, 我可以将麦克风、游戏音效、游戏语音分别设置成不同的音轨.(因为测试NVIDIA experience的关系, 我在使用OBS前已经安装了voice meter banana. 使用虚拟设备将游戏音效和语音分到了两个独立的虚拟设备上).

因为NVENC提供的编码策略已经足够高效了, 因此我已经可以直接将录制好的视频上传到视频网站且不用担心视频被二次压缩. 因此这条工作流甚至还省却了视频编辑的不少事情.

我还是很看好windows game bar未来的发展, 可以将语音聊天, 视频录制, 推流等功能都收纳进来. 不过就目前而言, 对于有复杂录制需求的场景, OBS显然还是最好的选择.

15年前, 我在qq空间写博客, 10年前, 我在新浪博客上写, 5年前我在WordPress上写. 今天, 我又换了一种不同的方式.

前言

距离六月份这个博客正式建立好, 又过去了两个月, 我终于记得把这篇建设之初就应该发上来的博文写好.

我特意起了这么一个有年代感的标题. 说是年代感而不是时代感是因为, 这个标题不是为了蹭上最新的热度而起的. 而是希望在多年以后重新翻捡出来时, 能看到过去岁月的痕迹. 在那些年里, 我认为有价值的东西, 我希望有些东西在过去很多年以后, 也还能保有价值.

理念

在这个博客建设之初, 我已经想好了一些原则, 这些是我从过往的经历中提炼出, 我认为重要的事情. 过去, 我没有太多选择的权利. 但如今, 作为一个互联网上的自由人, 我有充分的资源来实践这些我认为重要的理念.

  • 数据独立保存

    博文是发布在网络上的数据. 但没有一种网络是持久稳定的, 诚然时至今日, 不管是新浪微博还是qq空间都还存在. 但那也只是凑巧我在过去选了两家还算靠谱的博客站点. 不过从长远考虑, 我还是希望自己掌握内容.

    基于同样的理由, 对于数据的格式, 我也希望是开放的格式, 如纯文本、markdown或是html, 而不是word、Evernote或者某个网站数据库里的一个字段.

  • 好用的编辑器和轻量、便捷的发布流程

    各个站点稀奇古怪的富文本编辑器是很令我苦恼的东西, 每每面对都感觉是一场恶战, 更别提图文混排等要求. 我相信有朝一日web端的文本编辑工具可以做的和本地应用一样好(而且可能在数据同步等方面更有优势), 但显然还需要时间.

    而若是使用本地的文本编辑工具, 有一套便捷的发布流程是必要的. 在word中写完整篇博文, 排完版, 再复制粘贴到某个网页端的富文本编辑器中, 再处理一次格式问题, 这显然不是我希望的形式. 而且一旦存在数据两端存储的问题, 保持内容的一致就成了一件很烦人的事情.

实践

基于上述原则, 选定方案并不是很艰难的事情. 包括以下几点

  • 使用markdown格式写博文.

    纯文本太单调, 富文本又太重, 简单的markdown已经够用了. 虽说在几类简化的标记语言中, 相比md我更喜欢emacs的org mode, 但markdown已经够用, 而且更加流行, 眼下没有理由花额外的effort选择org mode, 而即使以后想换, 从简单的markdown切换到org mode, 我想一定比反过来更容易.

  • 单独建立博文仓库

    我的博文存放在blog这个仓库, 这个仓库很干净. 除了一个pipeline的配置文件以外, 其余的全部是内容产物. 而与博客站点相关的基础设施代码则放在hexo-site这个仓库. 后者相对而言不是那么重要, 假以时日如果我不用hexo了, 完全可以用另外一套基础设施来替换.

  • 使用git&原生的markdown编辑器撰写

    用git来管理内容, 完成跨平台的需求. 周末我可以在家里用windows电脑写; 工作日我可以在办公室里在MacBook上写; 出差或出游时, 我可能在平板电脑(Android或是iPad)上写; 甚至碎片时间, 我可以在手机上写. 用git做数据同步在碎片时间的利用并不如诸如Evernote等同步笔记软件方便, 但我更倾向于保留这种选择软件的自由.

    顺便一提, 在windows和mac上, 我一般用typora, 但也不排除会用vs code. 平板电脑和手机上通常用jotpad.

  • 使用pipeline发布博客

    2019年, 要说最大的不同是什么, 就是devops工具链终于可以应用到各方各面. 几年前静态博客流行的时候, 并非没有考虑过用git+jekyll的组合, 但没有devops pipeline的配合, 每次敲命令生成网站再手动发布的过程实在不像在写博客.

    好在这在现在都不是事了. 免费的Travis CI(针对开源仓库)就已经非常好用了. 我因为有私有仓库发布的需求, 所以用的是azure pipeline. 但从流程上来说是类似的. 因为有独立的两个仓库, 所以需要一个发布流水线拉去两个仓库的代码, 将博文放在合适的目录下, 并调用hexo生成站点. 最后再将站点发布到s3. (关于流水线搭建的内容比较多, 这里就不展开讲了, 后续再详细些吧.)

以后

每次尝试重新开始写博客, 感觉上都是一次新的历险, 有时可以走的很远, 有时会半途而废. 我希望这次能走的足够远, 因为这一次我规划了很长时间.

我期望在很远的未来, 还能看到过去留在这里的文字, 在整个互联网成为遗迹, 风干了以后, 这里还会留存可以凭吊当年的化石.

继上篇最后给s3增加了cdn之后, 访问性能其实还算堪堪够用, 剩下倒是些细枝末节的优化. 虽然俗话说过早的优化是万恶之源. 不过身为程序员的代码洁癖还是让我忍不住把这些问题处理掉.

发现问题

如何发现性能问题是第一步. 对于网页访问的响应速度分析, 最简单的粗暴的做法是通过浏览器控制台自带的响应时间捕捉数据. 以chrome开发工具为例

屏幕快照 2019-06-14 上午11.34.40

这里我测试的是加载部署在aws ec2上的hexo博客的情况, 并且使用的是手机4g作为热点. 可以看到加载时间长达27秒. 其中dom文档下载花费了15秒, 而所有资源(主要是图片)则是耗时长达27秒.

另外一个选项, 对于互联网网页, 可以用google的pagespeed insights对网页进行彻底的分析. 使用这个分析的另一个好处是, 在pagespeed insights上获得高评分有利于提高在搜索引擎上获得的权重.(即seo优化)

下图是本博客在优化前的评分情况.

pagespeed

除了提供评分以后, pagespeed insights还提供了很多优化建议. 比如延迟加载屏幕外图片, 在header中增加cache时间, 使用gzip做文本压缩等.

性能优化

优化图片加载

那么优化的第一步就以图片为例, 图片加载的优化路子有很多. 比如这里的图片大小高达800kb,虽然这已经是我在原始照片的基础上做了有损压缩的结果, 但是对于网页而言, 这个尺寸还是大了一点. 比较合适的做法应该是在网页上提供一个低保真的缩放(将图片分辨率降低比如800x600的尺寸, 并降低图片质量), 同时提供如点击后浏览大图的功能. 这也是我认为比较好的一个策略.

实施这一做法也有很多办法. 我搜到一个比较新奇的做法是用aws lambda做实时的图片处理. 这个做法的优势在于可以做任意尺寸的图片缩放. 但是采用实时压缩, 我个人觉得在响应速度上并不存在优势. 另一个做法是在hexo生成博客的时候, 同时生成多份图片资源, 比如生成如xx_tumb.jpg格式的缩略图供内嵌页面使用, 并在点击查看大图时, 使用xx.jpg的链接.

遗憾的是似乎没有现有的hexo插件可以提供这一功能, 暂时性的, 我使用的是hexo-lazyload-image这个插件, 间接避免了加载过多图片的问题. 当然这个插件依旧不能避免当首页有大图时的问题, 这个问题就留给以后优化了.

增加缓存时间

在header中增加CacheControl的做法有很多种. 这里我的做法是在将文件上传到s3时增加相应标签. 使用的部署插件是自己改写的s3部署插件, 然后注意在cloudfront中保留原始的header内容.

完成后通过浏览器访问, 并在控制台查看header中是否有对应内容即可.

增加gzip压缩

gzip压缩主要针对html页面或是js脚本等, 能提供比较客观的流量压缩. 如果是使用nginx或者apache等web服务器的话, 一般都有实时的gzip压缩功能. 不过遗憾的是s3并没有这个功能. 本来想着可以在部署阶段先通过gzip压缩, 然后再发布到s3上. 同时在header中增加encoding=gzip. 这当然也是一个可选的做法, 但是这么做的话, 如果客户端使用不支持gzip格式的浏览器(虽然可能性几乎为0), 那么就无法访问了.

好在s3虽然没有提供压缩功能, 但是cf提供了. 在cf中将压缩选项打开, 就会自动根据资源类型进行压缩.(只压缩html、js脚本等资源)

cf的变更需要一定时间生效. 稍后通过浏览器查看, 同样可以在response header中看到encoding相关内容. 另外, 也可以与压缩前的文件大小做对比, 文本文件通常可以降低2/3的大小.

复核pagespeed insights评分

pagespeed insights的评分有一定滞后性, 可能是google会对网页做缓存的缘故. 下图是上述优化都生效以后的评分情况.

屏幕快照 2019-06-14 上午11.15.11

这时候的评分就理想多了. 虽然还存在一个首次有效绘制时间过晚的问题. 猜测是next这个主题引起的, 要等到页面相关的资源(html、css、js等)全部就位以后才会绘制页面.

这个就暂时不管了, 等有时间研究next主题了, 再来考虑这个优化.