MP3 ID3 结构

ID3 是一个元数据(metadata) 的容器,通常和 MP3 文件一起。

ID3 有两个版本:

  • ID3v1
  • ID3v2

ID3v1

ID3v1 比较简单,存放在 MP3 文件末尾,占用 128 个字节,使用任意一个 16 进制编辑器打开 MP3,就可以看到。

V1 版本以 TAG 字符开始,记录了 MP3 文件的歌手名,标题,专辑名称,年代,风格等信息。

字节 长度 说明
1-3 3 TAG 字符,说明 ID3v1 开始
4-33 30 歌曲名
34-63 30 歌手
64-93 30 专辑名
94-97 4 年份
98-127 30 附注
128 1 音乐类别,147 种1

ID3v2

ID3v2 版本位于 mp3 开头,长度可变。

ID3v2 有四个版本

  • ID3v2.1
  • ID3v2.2
  • ID3v2.3
  • ID3v2.4

因为在文件开头,所以对 ID3v2 版本的操作要比 ID3v1 慢。

以 ID3v2.3 为例,由一个标签头和若干标签帧组成,至少有一个标签帧,每个标签帧记录一种信息,比如标题,作曲家等等。

可以通过 vim 打开 mp3 文件,然后执行 %!xxd 以 16 进制查看。

mp3 id3

文件开头长度 10 字节,结构如下:

char Header[3];    /*必须为“ID3”否则认为标签不存在*/
char Ver;         /*版本号ID3V2.3 就记录3*/
char Revision;     /*副版本号此版本记录为0*/
char Flag;        /*标志字节,只使用高三位,其它位为0 */
char Size[4];      /*标签大小*/

说明:

  • Flag,一般使用高三位
    • 第一位表示是否使用 Unsynchronisation
    • 第二位表示是否有扩展头部
    • 第三位是否是测试标签
  • 标签大小是指包括标签头的 10 字节在内的所有标签帧的大小,每个字节只使用后 7 位,最高位为 0
    • 计算大小时将最高位去掉,得到 28 位二进制数,就是标签大小

标签帧

每个标签帧都有 10 个字节的帧和至少一个字节的不固定长度的内容组成。

帧头部:

char FrameID[4];   /*用四个字符标识一个帧,说明其内容,稍后有常用的标识对照表*/
char Size[4];    /*帧内容的大小,不包括帧头,不得小于 1*/
char Flags[2];    /*存放标志,只定义了 6 位*/

说明:

  • 帧标识,四个字符标识一个帧
    • TIT2 歌曲标题
  • 大小,每个字节的 8 位全部使用
  • 标志,只定义了前 6 位

MP3 文件结构

大体上分三个部分:

  • ID3v2
  • 音频数据
  • ID3v1

附录

0="Blues";
1="ClassicRock";
2="Country";
3="Dance";
4="Disco";
5="Funk";
6="Grunge";
7="Hip-Hop";
8="Jazz";
9="Metal";
10="NewAge";
11="Oldies";
12="Other";
13="Pop";
14="R&B";
15="Rap";
16="Reggae";
17="Rock";
18="Techno";
19="Industrial";
20="Alternative";
21="Ska";
22="Deathl";
23="Pranks";
24="Soundtrack";
25="Euro-Techno";
26="Ambient";
27="Trip-Hop";
28="Vocal";
29="Jazz+Funk";
30="Fusion";
31="Trance";
32="Classical";
33="Instrumental";
34="Acid";
35="House";
36="Game";
37="SoundClip";
38="Gospel";
39="Noise";
40="AlternRock";
41="Bass";
42="Soul";
43="Punk";
44="Space";
45="Meditative";
46="InstrumentalPop";
47="InstrumentalRock";
48="Ethnic";
49="Gothic";
50="Darkwave";
51="Techno-Industrial";
52="Electronic";
53="Pop-Folk";
54="Eurodance";
55="Dream";
56="SouthernRock";
57="Comedy";
58="Cult";
59="Gangsta";
60="Top40";
61="ChristianRap";
62="Pop/Funk";
63="Jungle";
64="NativeAmerican";
65="Cabaret";
66="NewWave";
67="Psychadelic";
68="Rave";
69="Showtunes";
70="Trailer";
71="Lo-Fi";
72="Tribal";
73="AcidPunk";
74="AcidJazz";
75="Polka";
76="Retro";
77="Musical";
78="Rock&Roll";
79="HardRock";

80="Folk";
81="Folk-Rock";
82="NationalFolk";
83="Swing";
84="FastFusion";
85="Bebob";
86="Latin";
87="Revival";
88="Celtic";
89="Bluegrass";
90="Avantgarde";
91="GothicRock";
92="ProgessiveRock";
93="PsychedelicRock";
94="SymphonicRock";
95="SlowRock";
96="BigBand";
97="Chorus";
98="EasyListening";
99="Acoustic";
100="Humour";
101="Speech";
102="Chanson";
103="Opera";
104="ChamberMusic";
105="Sonata";
106="Symphony";
107="BootyBass";
108="Primus";
109="PornGroove";
110="Satire";
111="SlowJam";
112="Club";
113="Tango";
114="Samba";
115="Folklore";
116="Ballad";
117="PowerBallad";
118="RhythmicSoul";
119="Freestyle";
120="Duet";
121="PunkRock";
122="DrumSolo";
123="Acapella";
124="Euro-House";
125="DanceHall";

126="Goa";
127="Drum&Bass";
128="Club-House";
129="Hardcore";
130="Terror";
131="Indie";
132="BritPop";
133="Negerpunk";
134="PolskPunk";
135="Beat";
136="ChristianGangstaRap";
137="Heavyl";
138="Blackl";
139="Crossover";
140="ContemporaryChristian";
141="ChristianRock";
142="Merengue";
143="Salsa";
144="Trashl";
145="Anime";
146="JPop";
147="Synthpop";

reference

  1. 见附录 


2022-10-08 mp3 , mp3tag , id3 , id3v2 , metadata

将字幕压制到视频中

本文总结一下将字幕文件压制到视频中的方式,(当然我个人是非常不喜欢直接将字幕压制到视频流中作为硬字幕压制的,但有些时候可能就是需要分享这样硬字幕的视频,比如视频网站,所以也会在下文总结一下)。

按照压制方式可以分成,将字幕嵌入视频流(也就是俗称的硬字幕)适合在视频网站分享,将字幕作为单独的字幕流和视频作为封装格式,需要用播放器播放。

压制方式(推荐程度从上到下):

  • [[FFmpeg]] 适合熟悉命令行工具的人
  • [[HandBrake]] 开源的全平台的视频编码工具(推荐)
  • [[MKVToolNix]] 将字幕文件添加到视频中,但是作为软字幕,不改变视频流(推荐!)
  • [[MeGUI]] 只支持 Windows AVS 脚本生成器
  • 小丸工具箱 只支持 Windows
  • [[Arctime Pro]] Windows/macOS

如果要知道如何从 mkv 文件格式中提取字幕,可以参考 这篇文章

字幕类型

  • 外挂字幕:一般是一个外部的独立文件,一般有 srt, ass 等格式,播放视频时如果字幕文件与视频文件名一致,大部分的播放器会自动加载
  • 软字幕:也叫内挂字幕、封装字幕、内封字幕,字幕流等,就是把字幕文件嵌入到视频中,作为流的一部分,在播放视频文件时播放器会加载字幕,由用户选择使用哪一个字幕
  • 硬字幕:将字幕嵌入到视频流中合成一个文件,此时字幕成为视频画面的一部分,在任何播放器中都会显示该字幕,且用户无法关闭字幕。硬字幕存在的原因在于用户端播放器兼容问题,适合在所有播放器上播放,但缺点也是无法去除字幕

软字幕

作为字幕流(内封字幕、软字幕)嵌入到视频容器中。字幕流和视频和音频流具有相同的地位。视频格式中的 mkv 就是一种封装格式,通过 ffmpeg -i video.mkv 就能在输出结果中看到视频流和字幕流是属于不同的 stream 的。

MKVToolNix

MKV 封装工具:[[MKVToolNix]] MKV 提取工具:gMKVExtractGUI、MKVExtractGUI

借助 MKVToolNix 提供的界面操作即可。

FFmpeg

介绍一下如何使用 FFmpeg 将字幕作为单独的字幕流压制到视频中。

ffmpeg -i input.mkv -i subtitles.srt -c copy -c:s mov_text output.mp4
ffmpeg -i input.mkv -i subtitles.srt -c copy -c:s srt output.mkv

说明:

  • -c copy -c: s mov_text 告诉 FFmpeg 对于视频,音频,和字幕文件都直接 copy
  • 选项的顺序不能搞错,如果要随意顺序,那么可以显示指定 -c: v copy -c: a copy -c: s mov_text

假设原始输入文件没有字幕的情况下,也可以直接

ffmpeg -i input.mkv -i subtitles.srt -c copy output.mkv

FFmpeg 会自动识别字幕文件并做映射。

ffmpeg -i input.mp4 -sub_charenc 'UTF-8' -f srt -i input.srt -map 0:0 -map 0:1 -map 1:0 -c:v copy -c:a copy -c:s mov_text output.mp4
ffmpeg -i input.mp4 -sub_charenc 'UTF-8' -f srt -i input.srt -map 0:0 -map 0:1 -map 1:0 -c:v copy -c:a copy -c:s srt output.mkv

如果要指定语言:

ffmpeg -i input.mp4 -i subtitle.en.srt -c copy -c:s mov_text -metadata:s:s:0 language=eng ouptut_english.mp4
  • -metadata: s: s:0 设置 metadata 格式 Stream: Subtitle: Number 从 0 开始
  • language=eng 设置字幕的语言,使用 ISO 639 3 位英文表示

如果要设置多个字幕:

ffmpeg -i ouptut_english.mp4 -i subtitle.chi.srt -map 0 -map 1 -c copy -c:s mov_text -metadata:s:s:1 language=chi output_chi.mp4

在之前的基础之上,再添加一个中文字幕。

或者直接使用一行命令:

ffmpeg -i input.mp4 -i subtitle.en.srt -i subtitle.chi.srt -map 0 -map 1 -map 2 -c copy -c:s mov_text -metadata:s:s:0 language=eng -metadata:s:s:1 language=chi output_eng_chi.mp4

硬字幕

FFmpeg

使用硬编码将字幕嵌入视频的方式会更加耗时,需要重新编码文件。

[[FFmpeg]] 要在视频流上面加上字幕,需要用一个叫做 subtitles 的滤镜,要使用这个滤镜,在命令中写上 -vf subtitles=字幕文件名 ,推荐不管文件名如何都在字幕文件两边加上双引号,比如 -vf subtitles="字幕 文件名",因为如果文件名中包含空格或其他特殊字符,在不使用双引号的情况下 Shell 会解析失败。

# 使用 subtitles 滤镜为视频添加字幕,字幕文件在视频流中,输出文件不含字幕流
ffmpeg -i input.mkv -vf subtitles="subtitles.srt" output.mkv

说明:

  • -vf-filter: v 参数的缩写
  • subtitles="subtitle.srt" 则是 filter 的名字,后面是字幕文件

将 mkv 文件中的字幕压制到 mp4

# 将 input.mkv 中的字幕(默认)嵌入到 output.mp4 文件
ffmpeg -i input.mkv -vf subtitles=input.mkv output.mp4

如果要将其他的字幕,可以指定,比如第二个字幕:

ffmpeg -i input.mkv -vf subtitles=input.mkv:si=1 output.mkv

subtitle 更多 用法

ass 格式

如果要处理 ass 格式的字幕文件,那么需要 FFmpeg 启用 libass ,可以通过执行 ffmpeg —version 来查看输出中是否有 --enable-libass,如果没有这个选项那么可能需要重新安装,或者重新编译安装 FFmpeg 。

如果要嵌入 ass 格式字幕,可以:

brew install ffmpeg --with-libass
ffmpeg -i path/to/video.mp4 -vf "ass=subtitle.ass" out.mp4

ass 格式字幕文件提供了更多的格式选择,比如加粗,斜体,字体,颜色等等,可以使用更加专业的字幕制作软件生成。

reference


2022-10-02 subtitle , video , mp4 , fansub

解决 Clash for Windows 节点测速 timeout 问题

[[Clash for Windows]] 使用过程中一直没有什么问题,但是昨天心血来潮把 Clash for Windows 从 0.18.8 升级到了最新版本(0.20.5) ,然后发现节点全部 timeout。但可以排除的是这些节点肯定是可以用的,因为在手机上是完全没有问题的。

先是看 Logs 日志里面,timeout 的节点有大量的错误:

22:06:18 WRN [UDP] dial failed error=new vmess client error: dial xxxx:7830 error: 404 Not Found proxy=GLOBAL rAddr=114.114.114.114:53

查询了一通之后发现可能与 Clash Core 版本 升级 有关系,

查看了一下 Clash 的 Release Note ,在 1.90.0 的 Change Logs 中有一行:

注意vmess下的 ws-headers 和 ws-path 选项已更新

原来 Clash Core 新版本中把配置文件的 ws-headersws-path 改了个名字

  • ws-path
  • ws-headers

这两个配置项变成了如下的结构:

  ws-opts:
    path: /path
    headers:
      Host: somehost.com

完整配置示例:

# VMess
- name: "v2ray"
  type: vmess
  server: xxx
  port: 443
  uuid: 8b0edc
  alterId: 0
  cipher: auto
  # udp: true
  tls: true
  # skip-cert-verify: true
  network: ws
  ws-opts:
    path: /xxx
    headers:
      Host: xxxx.com

JSON 格式:

proxies:
    - { name: '美国', type: vmess, server: some.pw, port: 6000, uuid: ccfb9fb3, alterId: 0, cipher: auto, udp: true, network: ws, ws-opts: { path: /, headers: { Host: some.com } }, ws-path: /, ws-headers: { Host: some.com } }

如果不想自己配置,那么可以注册使用这个站点

其他 timeout 原因

另外一个可能引起 timeout 的原因可能是 Clash 的配置中开启了 DNS

dns:
    enable: true
    ipv6: false

开启了 DNS 之后,clash 会将域名解析发送给配置的 nameserver 解析,如果域名解析失败也会发生 timeout 情况。

其他原因:

  • 节点配置错误
  • 节点无法访问
  • 配置的 url-test 中的 url 设置错误
  • 系统时间不同步

2022-09-30 clash , vpn , mac , linux , cfw

Arc 浏览器初印象

很早之前就在 Twitter 上看到有人分享了 Arc 浏览器的使用体验,说是非常惊艳,我就稍微的浏览了一下官网,抱持怀疑的态度先注册了一下体验,一直好奇到了 2022 年能够在浏览器上做出什么样的创新,自 Chrome 横空出世以来,快,安全迅速抢占了浏览器市场。剩下的一点点份额被 Firefox,Safari,Edge,[[Vivaldi]] 等等占据,早两年的时候我也写过一篇标题略微耸动的文章 —- 我可能要抛弃用了很多年的 Chrome 改用 Vivaldi ,但事实是 3年多过去了,我日常用的还是 Chrome,虽然 Google 在浏览器插件,隐私等等问题上这两年来一直被诟病,但至少还没有彻底地激怒我这个用户。

但可能也是 Google 作为一家广告公司,这个背景实在会让人产生一些敬畏。所以这些年 Chrome 也面临了越来越多的挑战,不管是大企业的 Edge,Safari,亦或是初创企业的,比如本文的主角 Arc Browser ,或者注重隐私的 Orion ,或者在 UI 交互上做创新的 SidekickSigmaOS ,都给浏览器发展一些新的思路。这么多年过去 Chrome 还能克制能够抱持整体风格的简洁,并且让地址栏发展成为 Omnibox ,Chrome 几乎成为了我的 开机应用 ,并且借助 Chrome Extension 的生态,以及这些年 Web 技术的发展,在浏览器中能做的事情越来越多,很多 Native 的应用也越来越多地被替换成 Web 应用。这一次体验 Arc 最让我惊艳的就是一些应用,比如 Notion 在 Arc 中的体验就像是 Notion 本地应用一样(虽然 Notion 发布的本地应用就能就是套了一层壳而已)。

初上手

  • 非常亮眼的视觉设计
  • 友好的提示,正是因为交互元素上做出了一些调整,所以即使是用惯了传统浏览器的用户也需要经过导览才能使用,传统的在上方的地址栏被调整到侧边

新特性

Space

最开始让我想要了解的功能就是 Space,侧边栏通过双指滑动可以创建新的 Space, 也可以通过下方的 + 号来创建,但是使用之后体验上就感觉是分组的标签页。

Easel

Arc 内建了一个画板,可以做笔记,剪切网页图片,这就相当于内嵌了一个素材收集工具。在地址栏边上的截图小工具的交互设计做的确实不错,可以根据页面内容动态调整要截取的内容。

Easel arc-easel

Library

在侧边栏还有一个 Library 的概念,在 Library 中会展示下载,桌面等等文件夹,其中还包括了 Arc 独有的 存储 Easel 画板的地方,存储在 Arc 中抓取的网页截图。

Library arc-library

分屏

一些快捷键

大部分的快捷键,和使用 Chrome 都是一致的,打开标签页 Cmd+T, 关闭标签页 Cmd+w,恢复关闭的标签页 Cmd+Shift+t, 更多

但 Arc 引入的新功能必然带来新的快捷键,这里就列举一些常见的,全部的快捷键还是查看其官网,或 Cmd+, 查看吧

  • ⌘+s 锁定或隐藏侧边栏
  • Ctrl+Tab 切换标签页
  • Ctrl+Shift++ 创建垂直分屏

一些问题

注册问题

如果在注册的时候遇到 「Unknown server error」 的错误

arc browser register unknown server error

那么可能是网络无法访问 Arc 的服务,这个问题我在之前体验 Warp 终端 的时候也遇到了,现在出现的这些产品都喜欢在开篇的时候让用户注册账号使用,但是在国内的这种网络环境下就会遇到各种奇葩的问题。解决办法也非常简单,直接开启 系统全局代理 ,或者去一个没有 GFW 的地方。这里推荐使用 Clash for Windows

进度条问题

在 Chrome 中打开网页,我会看去 Tab 上状态,在加载的时候会有 Loading 的转圈,而在 Vivaldi 中则是更加明显的地址栏中会有色彩进度条,我会明确知道这个页面是正在加载的状态,而使用 Arc 第一个感到不适的就是当网络环境比较慢的时候,会有很长一段时间整个页面是空白状态,如果隐藏侧边栏最上方的地址栏就完全不知道是这个网站出错了还是网络有问题。虽然 Arc 刻意隐藏了所有「不必要」的内容,但却也带来了一些使用上的不便。

卡顿问题

相同的环境下,Chrome 从来没有发生过页面或交互中间出现卡顿的情况,但是 Arc 使用过程中隔一会儿就会出现。虽然看别人演示的时候都非常流畅,但是就我个人的使用来说这一点是无法忍受的,尤其是当我想打开标签页,快速输入一些内容进行查找时,这个延迟非常明显。

总结

今天花了一段时间体验了一下 Arc,总体来说视觉上,交互上确实带来了一些新鲜感,但似乎目前还不能被我设为默认浏览器,虽然分屏,Space 等等确实给浏览器交互带来了一些新的启发,但对于我而言这些功能并没有那么不可代替。所以短时间内我还是会继续用 Chrome,不过时常回来关心一下 Arc,看能不能带来一些划时代的革新。

如果你也想体验,可以去 官网 申请。或者使用我的邀请


2022-09-30 arc , browser , chrome , vivaldi

Javalin:一个轻量的 Web Framework

说起 Java 语言下的 Web 框架那就非 [[Spring Framework]] 不可了,但是今天在和别人在聊天的过程中发现了一个新奇的项目 Javalin。 Javalin 是一个轻量的 Web 框架。支持 [[WebSocket]], HTTP2 和异步请求。简单的看了一下官方的说明文档,确实非常轻量,几行代码就可以启动一个 HTTP 服务。

[[Javalin]] 最初是 [[SparkJava]] 的一个分支,后来受到 JavaScript 框架 koa.js 的影响,逐渐独立成一个新的项目发展。

首先来看看一个比 Hello World 稍微复杂一些的例子:

var app = Javalin.create(config -> {
    config.defaultContentType = "application/json";
    config.autogenerateEtags = true;
    config.addStaticFiles("/public");
    config.asyncRequestTimeout = 10_000L;
    config.dynamicGzip = true;
    config.enforceSsl = true;
}).routes(() -> {
    path("users", () -> {
        get(UserController::getAll);
        post(UserController::create);
        path(":user-id"(() -> {
            get(UserController::getOne);
            patch(UserController::update);
            delete(UserController::delete);
        });
        ws("events", userController::webSocketEvents);
    });
}).start(port);

验证路径参数

var myQpStr = ctx.queryParam("my-qp"); // 没有验证,返回字符串或空
var myQpInt = ctx.pathParam("my-qp", Integer.class).get(); // 返回一个整数或抛出异常
var myQpInt = ctx.formParam("my-qp", Integer.class).check(i -> i > 4).get(); // 整数 > 4

// 验证两个依赖的查询参数 :
var fromDate = ctx.queryParam("from", Instant.class).get();
var toDate = ctx.queryParam("to", Instant.class)
        .check(it -> it.isAfter(fromDate), "'to' has to be after 'from'")
        .get();

// 验证一个json消息体:
var myObject = ctx.bodyValidator(MyObject.class)
        .check(obj -> obj.myObjectProperty == someValue)
        .get();

handler

//前置handler
app.before(ctx -> {
    // 在所有请求之前运行
});
app.before("/path/*", ctx -> {
    // 在/path/*请求之前运行
});

//端点handler
app.get("/", ctx -> {
    // 一些代码
    ctx.json(object);
});

app.get("/hello/*, ctx -> {
    // 捕获所有对/hello/子路径的请求 
});

//后置handler
app.after(ctx -> {
    // 在所有请求之后运行
});
app.after("/path/*", ctx -> {
    // 在/path/*请求之后运行 
});

使用 AccessManager 接口来实现验证和授权。

如果要部署 Javalin 应用程序,开发人员只需创建一个包含了依赖(使用 maven-assembly-plugin)的 jar,然后用 java -jar filename.jar 发布该 jar。Javalin 自带一个嵌入式 Jetty 服务器,无需额外的应用程序服务器。

Javalin 还有 专门为教育工作者准备的页面 ,该页面强调学生可以从 Javalin 受益,因为 Javalin 提供了嵌入式的 Jetty 服务器,所以不需要 Servlet Container/Application 服务器配置就可以开始编码。

有一系列教程可供使用,如 Running on GraalVMKotlin CRUD REST API 。可以在 教程页面 找到完整的列表。

文档页面 提供了有关 Javalin 的更多细节。用户可以通过 maven 或从手动 maven中央库 下载 Javalin。

部署执行

通过 mvn package 就可以打包一个 jar 文件,直接运行 java -jar xxx.jar 就可以启动。

启动 7000 端口占用问题

因为我在 macOS 下启动 Javalin 程序,默认是使用的 7000 端口,但是起来的时候发现端口被占用了。

lsof 查看

❯ sudo lsof -nP -i4TCP |grep 7000
Password:
Swinsian   1563 einverne   36u  IPv4 0xa107511eb4d4e74b      0t0  TCP 127.0.0.1:50677->127.0.0.1:7000 (CLOSED)
Swinsian   1563 einverne   37u  IPv4 0xa107511eb4d4e74b      0t0  TCP 127.0.0.1:50677->127.0.0.1:7000 (CLOSED)
ControlCe  1578 einverne   29u  IPv4 0xa107511eb42171fb      0t0  TCP *:7000 (LISTEN)

查看进程

❯ sudo ps aux | grep 1578
einverne         46918   0.7  0.0 34253900    968 s000  S+    2:37PM   0:00.00 grep --color=auto 1578
einverne          1578   0.0  0.1 36594320  36324   ??  S    Sun12PM   1:24.15 /System/Library/CoreServices/ControlCenter.app/Contents/MacOS/ControlCenter

发现竟然是系统的 ControlCenter 占用了本地 7000 端口,用如下的方法禁用。

stop airplay take 7000 port

[[javalin-database]]

reference


2022-09-26 javalin , java , jetty , kotlin , web-framework , restful

使用开源 Wakapi 代替 WakaTime 统计编码时间

之前折腾 GitHub Profile 的时候发现了 [[WakaTime]] 这样一款统计编码时间的工具,之后在读 waka-readme 项目的时候发现,还有两个完全开源的后端兼容版本,一个是 Golang 编写的 [[wakapi]] ,一个是 Huskell 编写的 hakatime 。这篇就来总结一下我使用 wakapi 的过程。

wakapi 是一个兼容 [[WakaTime]] 的可自行架设的后端程序,和 WakaTime 一样可以用来统计代码。

Installation

使用 docker-compose 安装。

直接 clone 项目,修改环境变量,然后启动即可。

git clone https://github.com/einverne/dockerfile.git
cd dockerfile/wakapi/
cp env .env
# edit .env setup SALT and WAKAPI_DATA
# SALT 可以执行命令 cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w ${1:-32} | head -n 1
# WAKAPI_DATA 配置一个本地可读写的路径
docker-compose up -d

我的配置中没有暴露 3000 端口,我是和 Nginx Proxy Manager 一起使用的,在 Nginx Proxy Manager 后台,配置一个 HOST,设置 wakapi:3000 ,然后去 [[Cloudflare]] 后台将域名 wakapi.einverne.info 设置一个 A 记录指向 Nginx 所在的服务器。等待 DNS 生效,访问后台 wakapi.einverne.info 后台即可。

我个人会一直使用 https://wakapi.einverne.info 服务,所以如果你感兴趣,也可以直接使用这个服务。

服务启动之后,注册登录,然后就可以配置编辑器插件,把 IntelliJ IEDA,[[VSCode]],[[Vim]] 先配置上。这部分可以直接查看 WakaTime 的官方文档。

编辑客户端配置 ~/.wakatime.cfg ,因为使用 Self-hosted 的后端,所以需要设置 api_urlapi_key 则从后台获取即可。

[settings]
api_url=https://wakapi.einverne.info/api
api_key=b5b0xxx
proxy=
debug=false
status_bar_enabled=true

在 Obsidian 中使用 WakaTime

今天偶然在浏览 Obsidian 插件库的时候发现了 WakaTime 的插件,安装之后就和编程 IDE 一样,会直接使用 HOME 目录下的配置。所有的数据都可以完美的上传到 Wakapi 的后台。

GitHub Actions


2022-09-22 wakatime , wakapi , code , programming , self-hosted , linux , docker

使用 WakaTime 统计编码时间

[[WakaTime]] 是为程序员打造的编码统计 Dashboard,可以同来统计项目,编程语言,IDE,编码时间等等内容。 之前在折腾 GitHub Profile 的时候发现的,可以在 GitHub Profile 页面中动态的展示最近的编程状态。

WakaTime 可以统计的内容包括:

  • 每天在每个项目上的编码的时间
  • 使用的编辑器
  • 编程语言占比
  • 所使用的操作系统
  • 所在的项目

Price

WakaTime 基础使用是免费的,但有如下限制:

  • 只包含两个星期的历史
  • 有限的整合
  • 3 位朋友之间的 Leaderboards

对于 Premium 可以解锁更多的 功能

Config

WakaTime 的配置文件在 HOME 目录下的 .wakatime.cfg 文件中。

cat ~/.wakatime.cfg
[settings]
api_key=
proxy = 
debug = false
status_bar_enabled = true

IntelliJ IDEA (JetBrains 系列)

插件 Market 中搜索 wakatime,重启 IDEA,在弹出的 WakaTime Settings 中填入 API key。

如果要设置 proxy,可以在下面填入 http_proxy 类似:http://localhost:1080

Vim

Plug 'wakatime/vim-wakatime'

Self-hosted

  • [[wakapi]]

2022-09-21 wakatime , intellij-idea , code-stats

使用 yt-dlp 下载 YouTube 视频

yt-dlp 是 YouTube-DL 的进阶版本,延续了 YouTube-DL 的开发和维护。

如果想要下载哔哩哔哩 (Bilibili) 的视频,可以看看 lux 这款工具。

如果在 macOS 上还有一款不错的收费应用叫做 [[Downie]] 也可以用来下载 YouTube 视频。数码荔枝 上有正版授权,可以选择购买。

安装

安装 [[FFmpeg]]:

sudo apt install ffmpeg

安装 yt-dlp:

sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
sudo chmod a+rx /usr/local/bin/yt-dlp
sudo ln -s /usr/local/bin/yt-dlp /usr/local/bin/yt

使用

基础使用:

yt-dlp -F --proxy socks5://127.0.0.1:8080 https://www.youtube.com/watch?v=3Hq-cgsV7Og

下载最佳画质:

yt-dlp -f best URL

下载最高视频和音频并合并:

yt-dlp -f bestvideo+bestaudio URL

下载最高视频和音频分开两个文件:

yt-dlp -f 'bestvideo,bestaudio' URL

将视频缩略图嵌入到视频文件:

yt-dlp -f 'bv[height=1080][ext=mp4]+ba[ext=m4a]' --embed-thumbnail --merge-output-format mp4 https://www.youtube.com/watch?v=1La4QzGeaaQ -o '%(id)s.mp4'

下载播放列表:

yt-dlp -f 'bv*[height=1080]+ba' --download-archive videos.txt  https://www.youtube.com/playlist?list=PLlVlyGVtvuVnUjA4d6gHKCSrLAAm2n1e6 -o '%(channel_id)s/%(playlist_id)s/%(id)s.%(ext)s'

下载整个 YouTube Channel,保存为平台加视频名称

yt-dlp -f 'bv*[height=720]+ba' --download-archive videos.txt https://www.youtube.com/c/FootheFlowerhorn/videos -o '%(channel)s/%(title)s.%(ext)s'

2022-09-12 youtube , yt-dlp , video , 视频下载

折腾一下 GitHub Profile

虽然很早就知道 GitHub 发布了 Profile 功能,可以使用 README 来丰富 Profile 页面。但是一直以来没啥动力,大多数时候都不会去到主页去访问。但现在有些时候逛 GitHub 的时候会点到 其他人 的主页去看,发现有一些主页虽然只有寥寥几句,但却可以清楚的知道「他/她」最近在贡献什么内容,擅长什么技能。虽然我在 GitHub 上还是观摹大佬居多,但也想着通过这个契机在整理 GitHub Profile 的时候加深一下对自己的认知。

至于如何建立同名的 repository,如果提交代码就先略过了,官方的帮助和其他文章的内容都非常详细。

刚开始去 Google 「GitHub Profile」 就发现了如下的页面生成器 GitHub Profile Generator ,可以用这个生成器生成一个初始版本,然后在其基础上修改。

在调研的过程中基本发现了两大类主流的用法,一类是通过 GitHub 的 API ,或者其他服务的 API,生成一个 Badge 展示,另外一类就是通过 [[GitHub Actions]] 通过定时任务动态的使用代码聚合一些内容,然后再动态地展示到页面中。

因为 README 中可以直接写 HTML,所以如下的 HTML 也可以直接使用,注意替换其中的链接。

<h3 align="left">Connect with me:</h3>
<p align="left">
<a href="your link" target="blank"><img align="center" src="https://cdn.jsdelivr.net/npm/simple-icons@3.0.1/icons/twitter.svg" alt="" height="30" width="40" /></a>
<a href="your link" target="blank"><img align="center" src="https://cdn.jsdelivr.net/npm/simple-icons@3.0.1/icons/linkedin.svg" alt="" height="30" width="40" /></a>
<a href="your link" target="blank"><img align="center" src="https://cdn.jsdelivr.net/npm/simple-icons@3.0.1/icons/instagram.svg" alt="" height="30" width="40" /></a>
<a href="your link" target="blank"><img align="center" src="https://cdn.jsdelivr.net/npm/simple-icons@3.0.1/icons/youtube.svg" alt="" height="30" width="40" /></a>
</p>

或者使用外部的 Readme-stats 来生成一个数据卡:

<p>
  <a href="https://github.com/einverne/">
    <img  margin-top="-30px" width="55%" align="right" alt="einverne's github stats" src="https://github-readme-stats.vercel.app/api?username=einverne&show_icons=true&include_all_commits=true&count_private=true&layout=compact&hide_border=true" />
  </a>
</p>

图标

如果要在页面中放入图标可以到如下的网站寻找。

  • Simple Icons 是一个开源的 SVG 图标库,包含了上百个品牌的图标。
  • Skill Icons 是一组可以用来展示技能的图标。
  • Flaticon 提供了很多彩色的图标,也提供很多收费的图标,可以根据需要选择。
  • Icons8 同样是一个图标库,但是也包含一些收费的图标
  • Wikimedia Commons 是另一个不错的选择,可以找到很多官方的图标,并且可以自由使用。

另外一个寻找 icon 的方式就是利用 https://cs.github.com GitHub 的 Code Search,然后直接搜索,比如我想要找到豆瓣的 svg ,输入 douban.svg 然后就能找到。

其他徽章

github-readme-stats

github-readme-stats 是一个用来生成 GitHub 统计数据的工具,可以在页面上展示获得的⭐,提交的次数,总共的 PR 等等。

徽章

生成从构建,代码覆盖率,开源协议,到社交网络等等,非常多的徽章。

统计页面访问量

如果想要统计访问 GitHub Profile 的数量,可以使用 GitHub Profile Views Counter 这个项目。

显示奖杯

GitHub Profile Trophy

样例:

github-readme-streak-stats

Streak stats

https://skyline.github.com/

GitHub Actions

基于 GitHub Actions 动态生成内容展示在 Profile 页面。

因为有 GitHub Actions,所以简单的用脚本可以展示

  • 最近在阅读的图书
  • 最近分享的文章
  • 当前正在听的音乐

等等。

显示最近博客内容

可以使用 blog-post-workflow 来显示最近更新的博客内容。通过定时读取 Feed ,来在页面动态展示内容。

GitHub 最近动态

GitHub Activity Readme 可以在页面上显示最近在 GitHub 上的动态。

Waka

如果你使用 [[WakaTime]] 来统计编码时间,那么可以使用 waka-readme-stats 来展示。

总结

最终的效果见 https://github.com/einverne/

reference


2022-09-10 github , git , github-profile , online-profile

现代政治的正当性基础 读书笔记

怎么知道的这一本书

似乎是在豆瓣的推荐流中第一次发现这一本。

关于作者

作者是[[周濂]],我还是在有关哲学的播客中第一次知道这一位人大的教授,首先接受的是他的声音,然后在豆瓣纪念[[江绪林]] 的文章中再一次看到了周濂教授,最后是在刘擎的 [[西方现代思想讲义]] 中又一次读到周濂。

几句话总结书的内容

什么是正当性 legitimacy

关于国家正当性的问题,要回答的问题是「在国家诞生的过程中需要满足哪些限制条件?」

legitimacy 正当性,合法性。怎么样产生的国家才在道德上是可以被接受的。正当性是一个「回溯」的概念,从「发生」追问国家的谱系、来源。

什么是证成性 justification

关于国家证成性的问题,要回答的问题是,「为什么需要国家」,国家这种制度存在的目的是什么?为什么一定要建立国家?无政府状态会更好吗?国家作为工具或手段能为公民提供什么样的好处?

国家具有哪些性质,使得它的存在是值得的。

国家证成性问题是一个「前瞻性」的概念,评价国家的功能,和国家的产生没有关系。

义务

  • 义务是通过履行某些自愿的行动(或者不作为)创造出来的道德要求
  • 义务是某类特殊的人对另一类特殊人的欠负
  • 每项义务都与一项权利相关联
  • 一个行动之所以是一种义务,是因为债权人和债务人所参加的互动或关系而形成,而不是因为被要求的行为在道德上的属性

政治义务

  • 特殊的道德义务,履行自愿的行为创造出来的道德要求
  • 特定政治共同体的公民,成员,或参与者
  • 政治义务的形成乃是因为国家和公民之间的互动或关系的本质所导致的
  • 公民负有政治义务与统治者拥有的统治权利相对应,也因此与国家拥有的正当性具有逻辑关联性
  • 代价很大,成本极高的义务

启发或想法

国家和政府首先是一个目的性的存在,人们建立国家和政府是为了促进自由正义、保障社会稳定和提高人民福祉,一旦一个民选政府或民主国家无法实现这些基本目的,那么人们就会收回当初的认可,这个国家和政府也就丢失了正当性。

谁应该看这本书

  • 想了解什么是[[国家]]的人
  • 想知道[[国家]]是怎么诞生的人
  • 想回答「我们是否有道德义务遵守法律」

印象深刻的句子

  • 「因为没有一个现存国家和政府拥有百分百的正当性,并不能推出所有现存的国家都不具有正当性,这就好象没有一个现实中的三角形是完美的,并不能推出所有现实中的三角形都不是三角形。」

2022-09-08 politics

电子书

最近文章

  • 阿加莎・克里斯蒂自传 怎么知道的这一本书
  • J3455 主板无法使用 PCIe 扩展 SATA 启动系统解决 把自己之前用 J3455-itx 主板组的 NAS 搬回了,但突然无法启动,这里就记录一下排错的过程。
  • .idea 文件夹的内容梳理 使用 JetBrains 旗下的 IDE 创建项目都会在项目的根目录中自带一个隐藏的 .idea 文件夹,每一次遇到这个文件夹的时候都会犹豫一下是否需要下面的内容全部放入到 .gitignore 文件中,大部分的时候就直接全部忽略了。现在想过来再了解一下这个文件夹下的每个文件都代表什么内容,因为有一些数据库配置,还有一些插件的临时信息都会存放在这个目录下。
  • Java 11 新特性学习 Java 11 在 2018 年 9 月 25 日发布。这是 Java 8 之后首个长期版本。
  • 《天朝的崩溃:鸦片战争再研究》读书笔记 《天朝的崩溃:鸦片战争再研究》 是茅海建教授对于鸦片战争研究的集大成之作,是系统研究鸦片战争十余年所得的成果。在阅读之前,我本以为是一部非常学术的类似论文一样的著作,然而我只阅读了一章就改变了我的观点,作者以人物为核心,将历史事件以人物为核心串联,并收集各方面的文献材料,去还原历史的本来面貌,去摆正对一个历史人物的是非判断。并且作者的笔法流畅通顺,非常易读。