Skip to main content

· 4 min read

背景

现场的展会,需要同步播放多个视频。 要求多个视频的播放进度必须保持一致。

解决方案

  • 建议使用windows笔记本作为主要pc控制端。
  • 选择一款可以同时播放多个视频的播放器,下面会给出选择。
  • 拼接多个显示屏幕(显示器或者投影仪)为一个屏幕墙,屏幕墙在操作系统中会识别成一个显示屏。一个播放器只能在一个显示屏上播放。

屏幕拼接接入

连接相关屏幕,有两种方式:有线与无线。

  • 有线:优点是稳定的数据传输,缺点是受拼接处理器和HDMI线的限制,能够连接的屏幕数量以及连接距离都会比较受限。
  • 无线:优先是解决了无线的缺点,缺点就是信号不够稳定,有时候视频会中断重连。

两种方式都可以配置多个屏幕为一个屏幕墙,在pc的显示配置中,可以看到接入一个分辨率非常大的显示屏。

有线

购买多屏拼接处理器,使用hdmi线组成一个显示器。(429元) image.png

  • 具体的配置可与客服沟通。

无线

使用spacedesk对同一个WIFI下的安卓/IOS/PC,设置进行投射,组成一个显示器。 建议购置一个稳定的路由器,否则可能会出现网络问题。推荐一个我在用的企业级路由器网件R8500价格428,测试了一晚上,没有出现断连问题。 image.png

播放器

选择随意一个可以播放多个视频的播放器即可,下面是两个测试过的。

hammultiplayer

官网 更为专业的软件,PC端可以作为统一监控界面。 学习成本高。 image.png image.png

gridplayer

这是一个更为简单控制的播放器。 开源项目地址 下载链接

  1. 打开下载好的播放器文件。

image.png

  1. 在播放器上点击右键,选择视频的排列方式。

image.png

  • 请注意配置参数 size ,选择一行最多可显示的视频个数。
  1. 拖动要同时播放的文件到窗口内。

image.png

  1. 右键:all 操作,统一操作视频。

image.png

  • play/pause: 统一控制 播放/暂停。
  • jump: 统一跳转到对应播放进度。

· One min read

RSSHub是一个配置rss服务的开源项目。 目前有好几个项目会有抓取微信公众号文章的功能。 image.png

我们测试一下使用CareerEngine的来源来查看券商中国的文章,对应的URL为: https://rsshub.app/wechat/ce/5a803af47718ea2ca9276f9a

只要订阅上方的url就可以看到相关公众号近期的文章。

image.png

rss作为一个公共协议,rsshub作为一个开源项目都是可以做一定的二次开发。 用来做数据的收集,也是可以的。

· 3 min read

centos 安装 nvidia-docker 并测试 tensorflow 和 pytorch

docker环境

如果系统没有环境,就安装环境。已经有了环境,最好升级到最新版本。

安装docker

  • 安装yum工具
$ sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2
  • 增加docker仓库到yum中
$ sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
  • 安装docker
$ sudo yum install docker-ce docker-ce-cli containerd.io
  • 启动docker
$ sudo systemctl start docker
  • 配置自启动
$ sudo systemctl enable docker
  • 测试安装情况
$ docker --version

升级docker版本

$ sudo yum upgrade docker-ce docker-ce-cli containerd.io

升级docker版本的时候请注意,会导致已经创建的容器无法启用,需要重新创建容器。

安装 nvida-docker

$ distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
$ curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.repo | sudo tee /etc/yum.repos.d/nvidia-docker.repo

$ sudo yum install -y nvidia-container-toolkit
$ sudo systemctl restart docker
  • 测试
$ docker run --rm --gpus all nvidia/cuda:9.0-base nvidia-smi

如果打印出下方类似的图片,也就是目前的宿主机的GPU,就说明安装正常。

可能出现的问题

安装 nvidia-container-toolkit 的时候报错 [Errno -1] repomd.xml signature could not be verified for libnvidia-container

修改 /etc/yum.repos.d/nvidia-docker.repo 文件 repo_gpgcheck 设置为0.

tensorflow测试

  • 创建一下临时的docker容器,并进入容器的运行环境。
@ docker run --rm -it --gpus all  tensorflow/tensorflow:1.15.0-gpu-py3 bash

运行python进入环境,并执行下面来自官网的代码例子。

import tensorflow as tf
mnist = tf.keras.datasets.mnist

(x_train, y_train),(x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=(28, 28)),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(10, activation='softmax')
])

model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])

model.fit(x_train, y_train, epochs=5)
model.evaluate(x_test, y_test)

试试看能代码能够正常运行下去。

pytorch测试

  • 创建一下临时的docker容器,并进入容器的运行环境。
@ docker run --rm -it --gpus all  pytorch/pytorch:1.3-cuda10.1-cudnn7-runtime bash

运行python进入环境,并执行下面来自官网的代码例子。

from __future__ import print_function
import torch
x = torch.rand(5, 3)
print(x)

试试看能代码能够正常运行下去。

· One min read

版本 docker 19.0.3

增加下列选项

{

"storage-driver": "devicemapper",
"storage-opts": ["dm.basesize=50G"]
}
{
"storage-driver": "overlay2",
"storage-opts": ["overlay2.size=50G"]
}

image.png

· One min read

在使用tts生成音频的时候,客户需要使用 64kbps的8k音频。 本来我以为只要使用 -ab 来指定就好了 image.png 发现音频怎么也改不过去, 然后意识到这个是 视频的参数。

在经过一定的搜索以后发现,不同的比特率对应的是不同的codecs。

codecformat
128kbpspcm_s16lelcpm
64kbpspcm_alawalaw

pcm_s16le 和 pcm_alaw 到底有什么区别呢?

image.png image.png 简单的理解,应该是使用在不通场景的编码格式。

· 2 min read

使用black来进行格式化

https://github.com/psf/black

在vscode中配置

20210913084128.png

在pycharm或者idea中配置

  1. Install black. `$ pip install black`
  2. Locate your black installation folder. On macOS / Linux / BSD: `$ which black /usr/local/bin/black # possible location On Windows: **$ where black %LocalAppData%\Programs\Python\Python36-32\Scripts\b*lack.exe # possible location* Note that if you are using a virtual environment detected by PyCharm, this is an unneeded step. In this case the path to black is $PyInterpreterDirectory$/black`.
  3. Open External tools in PyCharm/IntelliJ IDEA On macOS: PyCharm -> Preferences -> Tools -> External Tools On Windows / Linux / BSD: File -> Settings -> Tools -> External Tools
  4. Click the + icon to add a new external tool with the following values:
  • Name: Black
  • Description: Black is the uncompromising Python code formatter.
  • Program: <install_location_from_step_2>
  • Arguments: "$FilePath$"
  1. Format the currently opened file by selecting Tools -> External Tools -> black.
  • Alternatively, you can set a keyboard shortcut by navigating to Preferences or Settings -> Keymap -> External Tools -> External Tools - Black.
  1. Optionally, run Black on every file save:
  2. Make sure you have the File Watchers plugin installed.
  3. Go to Preferences or Settings -> Tools -> File Watchers and click + to add a new watcher:
    • Name: Black
    • File type: Python
    • Scope: Project Files
    • Program: <install_location_from_step_2>
    • Arguments: $FilePath$
    • Output paths to refresh: $FilePath$
    • Working directory: $ProjectFileDir$
  • In Advanced Options
    • Uncheck “Auto-save edited files to trigger the watcher”
    • Uncheck “Trigger the watcher on external changes”

· 3 min read

PYC编码

使用python自带的编译命令,编译成对应的二进制程序,还是可以被反编译,但是至少无法被直接查看。

pyc除了看不到源代码,其他使用上和py文件,没有太大差异。

在settools打包egg

# 打包
python ./setup/all.py bdist_egg --exclude-source-files

# 运行
egg_path='/home/shahid/suds_2.4.egg'
sys.path.append(egg_path)
import suds

suds.run()

普通情况下的编译

这种方式比较麻烦,因为pyc作为临时文件,是不推荐直接删除python文件。需要自己编写脚本完成下面部署:

  1. py_compile 强制编译生成pyc.
  2. 删除py文件。
python -m compileall .

find . -name "__pycache__" -exec rm -f {} \;
find . -name "*.py" -exec rm -rf {} \;

CPYTHON

使用cpython将python代码编译成C/C++,然后再编译成python扩展模块,windows上为pyd文件,Linux上为so文件.

打包成可执行文件

发现pyinstaller这个打包工具比较受人欢迎,经它打包后的exe文件可以在无python的环境下运行。

如果项目是只是服务中的依赖包例如egg就无法实现了。

代码混淆

命令: pyminifier

在线:http://pyob.oxyry.com/

代码混淆方案的缺陷在于,只能针对一个文件进行混淆,这样作为一个项目互相调用肯定有问题。(类名和方法名会变化)

需要针对需要混淆的文件,进行模块化设计。

虚拟机封装

在虚拟机安转好以后,修改登录密码,并且不告知客户。所有对虚拟机的操作通过开发服务进行调用。

· 7 min read

REST 简介

在 2000 年,Roy Fielding 提议使用表述性状态转移 (REST) 作为设计 Web 服务的体系性方法。 REST 是一种基于超媒体构建分布式系统的架构风格。 REST 独立于任何基础协议,并且不一定绑定到 HTTP。 但是,最常见的 REST 实现使用 HTTP 作为应用程序协议,本指南重点介绍如何设计适用于 HTTP 的 REST API。

基于 HTTP 的 REST 的主要优势在于它使用开放标准,不会绑定 API 的实现,也不会将客户端应用程序绑定到任何具体实现。 例如,可以使用 ASP.NET 编写 REST Web 服务,而客户端应用程序能够使用任何语言或工具来发起 HTTP 请求和分析 HTTP 响应。

原则

下面是使用 HTTP 设计 RESTful API 时的一些主要原则:

  • URL中不能有动词
  • URL结尾不应该包含斜杠“/”
  • URL路径中首选小写字母
  • URL路径名词均为复数

请求

所有后台的服务的请求需要加上 api 的前缀,来表示接口。

api.xxx.com/api/

版本

API 的版本号和客户端 APP 的版本号是毫无关系的,不要让 APP 将它们用于提交应用市场的版本号传递到服务器,而是提供类似于v1、v2之类的 API 版本号。版本号只允许枚举,不允许判断区间。

版本号拼接在 URL 中或是放在 Header 中都可以。例如:

api.xxx.com/api/v1/users

方法

HTTP 协议定义了大量为请求赋于语义的方法。 大多数 RESTful Web API 使用的常见 HTTP 方法是:

  • GET 检索位于指定 URI 处的资源的表示形式。 响应消息的正文包含所请求资源的详细信息。
  • POST 在指定的 URI 处创建新资源。 请求消息的正文将提供新资源的详细信息。 请注意,POST 还用于触发不实际创建资源的操作。
  • PUT 在指定的 URI 处创建或替换资源。 请求消息的正文指定要创建或更新的资源。
  • PATCH 对资源执行部分更新。 请求正文包含要应用到资源的一组更改。
  • DELETE 删除位于指定 URI 处的资源。

path设计

一般来说 API 的外在形式无非就是增删改查(当然具体的业务逻辑肯定要复杂得多),而查询又分为详情和列表两种,在 RESTful 中这就相当于通用的模板。例如针对文章(Article)设计 API,那么最基础的 URL 就是这几种:

  • GET /articles: 文章列表
  • GET /articles/id:文章详情
  • POST /articles/: 创建文章
  • PUT /articles/id:修改文章
  • DELETE /articles/id:删除文章

RESTful 中使用 GET、POST、PUT 和 DELETE 来表示资源的查询、创建、更改、删除,并且除了 POST 其他三种请求都具备幂等性(多次请求的效果相同)。需要注意的是 POST 和 PUT 最大的区别就是幂等性,所以 PUT 也可以用于创建操作,只要在创建前就可以确定资源的 id。

将 id 放在 URL 中而不是 Query Param 的其中一个好处是可以表示资源之间的层级关系,例如文章下面会有评论(Comment)和点赞(Like),这两项资源必然会属于一篇文章,所以它们的 URL 应该是这样的:

评论:

  • GET /articles/aid/comments: 某篇文章的评论列表
  • GET /comments/cid: 获取
  • POST /articles/aid/comments: 在某篇文章中创建评论
  • PUT /comments/cid: 修改评论
  • DELETE /comments/cid: 删除评论

这里有一点比较特殊,永远去使用可以指向资源的的最短 URL 路径,也就是说既然/comments/cid已经可以指向一条评论了,就不需要再用/articles/aid/comments/cid特意的指出所属文章了。

点赞:

  • GET /articles/id/like:查看文章是否被点赞
  • PUT /articles/id/like:点赞文章
  • DELETE /articles/id/like:取消点赞

RESTful 中不建议出现动词,所以可以将这种关系作为资源来映射。并且由于大部分的关系查询都与当前的登录用户有关,所以也可以直接在关系所属的资源中返回关系状态。例如点赞状态就可以直接在获取文章详情时返回。注意这里我选择了 PUT 而不是 POST,因为我觉得点赞这种行为应该是幂等的,多次操作的结果应该相同。

资源字段查询

/api/v1/users/?gender=1 直接作为gender来传入

资源统计

路径:/api/tableName/count 返回:

{
"code": 100,
"msg": "成功",
"data": {
total:100
}
}

参数设计

分页请求

/api/v1/users/?offset=10&limit=10 offset 表示偏移量,limit返回数据个数

排序

-表示降序排列的方式,不加-表示升序 /api/v1/users/?sort=-create_date

根据多个字段排序使用 /api/v1/users/?sort=-create_date,update_date

响应

HTTP CODE

尽量使用 HTTP 状态码,常用的有:

200:请求成功 201:创建、修改成功 204:删除成功 400:参数错误 401:未登录 403:禁止访问 404:未找到 500:系统错误

但是有些时候仅仅使用 HTTP 状态码没有办法明确的表达错误信息。

返回结构

code 代表系统内具体的编码,msg 代表对应的信息。

  • 成功时:
{
"code": 100,
"msg": "成功",
"data": {}
}
  • 失败时:

{ "code": -1000, "msg": "用户名或密码错误" }


分页

{
"code": 100,
"msg": "成功",
"total":100
"message": []
}

参考

· 3 min read

背景

我们在爬取数据的时候,有些请求的参数是使用各种特殊方式拼接出来的。 这样在我们不了解js中的逻辑下,我们要怎么获取到相关的请求参数呢?

在同一个session下的一些参数一般是固定的,故我们可以从前面的请求中获取请求或者返回参数。然后使用代码模拟请求来进行数据爬去。

获取返回参数

下面有两段代码需要处理。

Ajax-hook代码

// 拦截注入
!function(t){function n(e){if(r[e])return r[e].exports;var i=r[e]={exports:{},id:e,loaded:!1};return t[e].call(i.exports,i,i.exports,n),i.loaded=!0,i.exports}var r={};return n.m=t,n.c=r,n.p="",n(0)}([function(t,n,r){r(1)(window)},function(t,n){t.exports=function(t){var n="RealXMLHttpRequest";t.hookAjax=function(t){function r(n){return function(){var r=this.hasOwnProperty(n+"_")?this[n+"_"]:this.xhr[n],e=(t[n]||{}).getter;return e&&e(r,this)||r}}function e(n){return function(r){var e=this.xhr,i=this,o=t[n];if("function"==typeof o)e[n]=function(){t[n](i)||r.apply(e,arguments)};else{var u=(o||{}).setter;r=u&&u(r,i)||r;try{e[n]=r}catch(t){this[n+"_"]=r}}}}function i(n){return function(){var r=[].slice.call(arguments);if(!t[n]||!t[n].call(this,r,this.xhr))return this.xhr[n].apply(this.xhr,r)}}return window[n]=window[n]||XMLHttpRequest,XMLHttpRequest=function(){var t=new window[n];for(var o in t){var u="";try{u=typeof t[o]}catch(t){}"function"===u?this[o]=i(o):Object.defineProperty(this,o,{get:r(o),set:e(o),enumerable:!0})}this.xhr=t},window[n]},t.unHookAjax=function(){window[n]&&(XMLHttpRequest=window[n]),window[n]=void 0},t.default=t}}]);

这个是Ajax-hook项目的代码,如果我们使用\<script>的标签来进行注入,有时候回触发网站的安全机制,所以推荐直接从控制台运行整段代码。

请求回调配置

/**
* 打印xhr对象
* @param {*} xhr
*/
function printXhr(xhr) {
console.log("printXhr");
console.log("XMLHttpRequest", xhr);
console.log("XMLHttpRequest.responseText", xhr.responseText);
}

// 设置拦截回调
hookAjax({
open: function(arg, xhr) {
console.log("open", arg);
printXhr(xhr);
},
//拦截回调
onreadystatechange: function(xhr) {
console.log("onreadystatechange called");
printXhr(xhr);
},
onload: function(xhr) {
console.log("onload called");
printXhr(xhr);
}
});
  • open:请求发起的回调,可以在args中获取一些参数。
  • onreadystatechange:返回响应结果时被调用。
  • onload:jQuery是回调的onload(),而不是onreadystatechange()。

所以建议同时拦截onload()和onreadystatechange()来编写逻辑。

拦截请求例子

在selenium中取得数据

  • 查询console打印日志,通过关键字查询出想要的数据。
  • 在回调中增加dom,把相关参数写入,在在selenium获取相关dom的属性。

相关参考

Ajax-hook项目(请求拦截) XMLHttpRequest说明

· 4 min read

three.js

Three.js是纯渲染引擎,而且代码易读,容易作为学习WebGL.3D图形.3D数学应用的平台,也可以做中小型的重表现的Web项目。

但如果要做中大型项目,尤其是多种媒体混杂的或者是游戏项目VR体验项目,Three.js必须要配合更多扩展库才能完成,因为你可能会需要联网通信功能的封装.声音普通控制甚至高级频谱控制.输入设备信息的处理等诸多渲染以外的功能。这时候,就比较适合使用Babylon.js或者国内的一些针对游戏和多媒体应用开发的引擎或者说框架,例如LayaAir以及Egret3D

Hightopo

信息

http://www.hightopo.com/blog/461.html 数百个 HTML5 例子学习 HT 图形组件 掌握 HT 基础:

  1. 先入门手册 http://www.hightopo.com/guide/guide/core/beginners/ht-beginners-guide.html
  2. 看数据模型 http://www.hightopo.com/guide/guide/core/datamodel/ht-datamodel-guide.html
  3. 阅矢量手册 http://www.hightopo.com/guide/guide/core/vector/ht-vector-guide.html
  4. 读数据绑定 http://www.hightopo.com/guide/guide/core/databinding/ht-databinding-guide.html
  5. 序列化机制 hightopo.com/guide/guide/core/serialization/ht-serialization-guide.html

如有三维需求:

  1. 入门手册 http://www.hightopo.com/guide/guide/core/3d/ht-3d-guide.html
  2. 建模手册 http://www.hightopo.com/guide/guide/plugin/modeling/ht-modeling-guide.html
  3. OBJ 导入 http://www.hightopo.com/guide/guide/plugin/obj/ht-obj-guide.html

thing.js

image.png

  1. 更合理的API接口设计
  2. 在线编辑器。
  3. 3D模型可使用 3Dmax等工具来创建,并导入。
  4. 自带的快捷界面库更好。

对比

  1. 2D支持:things是一个完整的3D框架,没有2D相关的支持,hightopo本身支持2D和3D。
  2. 模型:在现实中所有的3D模型
    1. hightopo
      1. 没有太多模型
      2. 支持obj格式,可使用常见3D建模软件开发。
        1. 也可完全自定义模型的定义需要定义所有的点和动画
    2. thingjs
      1. 已有上千免费模型,和一些可选付费模型。
      2. 场景定义:可创建一些模型的组合。
      3. 支持obj,gltf 多种格式,可使用常见3D建模软件开发。
  3. 文档API:在文档和API提供商,thingjs这边的API设计更加的合理,文档更好理解,例子的代码注释也比较清晰。
  4. 组件:都可使用官方组件或者自定义组件。thingjs的官方组件更漂亮。
  5. 数据结构:数据结构差不多,但是 thingjs的结构更加清晰简单。文档说明清楚。 hightopo的属性都是堆在一块,可能上手成本比较高。

框架选择

  1. 是否需要2D功能:是的话 最好hightopo。
  2. 需要做的东西是否高度匹配hightopo 的例子。
  3. 其他情况下 things 占优。

工作流程

  1. 需要所需模型
  2. 创建场景。
  3. 数据对接:对接当前设备的信息,修改对应设备的状态。
  4. 增加组件界面。
    1. 增加
    2. 修改
    3. 位移