日志

日志

莹夜 ·

ubuntu 20.04使用netplan添加he的ipv6通道

network:
  version: 2
  tunnels:
    he-ipv6:
      mode: sit
      remote: endpoint服务器ip
      local: 本机ip
      addresses:
        - "address"
      gateway6: gateway
莹夜 ·

使用com.gluonhq:gluonfx-maven-plugin插件生成exe文件报以下错误。

java.lang.UnsatisfiedLinkError: no awt in java.library.path

解决方案:从JAVA_HOME下复制awt.dlljava.dllserver/jvm.dll这三个文件到exe所在的文件夹。
但接下来一般会伴随着java.lang.NoSuchFieldError …,还是不去使用awt/swing相关的内容吧。

莹夜 ·

java时区转换 String -> Date -> Calendar -> Instant -> LocalDateTime,用起来感觉很麻烦。

String uploadDate = "2023-01-20T04:46:00+00:00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
Calendar calendar = Calendar.getInstance();
calendar.setTime(sdf.parse(uploadDate));
LocalDateTime localDateTime = LocalDateTime.ofInstant(calendar.toInstant(), ZoneId.of("Asia/Tokyo"));
System.out.println(localDateTime.getHour());
莹夜 ·

Lapce一个文本编辑器,打开速度很快,可以像VSCode那样安装众多语言的开发插件,且同样支持远程开发。

Windows版本分为单文件版和安装版。

莹夜 ·
<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>PaperMC版本选择</title>
  <style type="text/css">
    * {
      box-sizing: border-box;
      padding: 0;
      margin: 0;
    }

    select {
      outline: none;
      width: 110px;
    }

    #app,
    #statusText,
    #downloadSlot {
      margin-top: 20px;
      text-align: center;
    }
  </style>
</head>

<body>
  <div id="app">
    <div id="version">
      <span>选择游戏版本: </span>
      <select id="versionsSlot"></select>
    </div>
    <div id="build">
      <span>选择文件版本: </span>
      <select id="buildsSlot"></select>
    </div>
  </div>
  <p id="downloadSlot">
    <button id="download">下载</button>
  </p>
  <p id="statusText"></p>

  <custom-tag text="hello web component"></custom-tag>
</body>

<script type="text/javascript">
  (() => {
    /** @type {HTMLSelectElement} */
    const versionsSlot = document.querySelector("#versionsSlot");
    /** @type {HTMLSelectElement} */
    const buildsSlot = document.querySelector("#buildsSlot");
    /** @type {HTMLParagraphElement} */
    const statusText = document.querySelector("#statusText");
    /** @type {HTMLButtonElement} */
    const downloadBtn = document.querySelector("#download");

    function selectStatusChange(b) {
      versionsSlot.disabled = b;
      buildsSlot.disabled = b;
      downloadBtn.disabled = b;
      if (b) {
        statusText.innerText = "加载中..."
      } else {
        statusText.innerText = "";
      }
    }

    async function init() {
      selectStatusChange(true);

      /** @type {Array<String>} */
      const versions = await getAllVersion();
      appendVersion(versions);

      /** @type {Array<String>} */
      const builds = await getBuilds(versions[0]);
      appendBuild(builds);

      versionsSlot.onchange = async () => {
        selectStatusChange(true);
        const builds = await getBuilds(versionsSlot.value);
        buildsSlot.innerHTML = "";
        appendBuild(builds);
        selectStatusChange(false);
      }
      downloadBtn.onclick = async () => {
        selectStatusChange(true);
        await getJar(versionsSlot.value, buildsSlot.value);
        selectStatusChange(false);
      }

      selectStatusChange(false);
    }

    function appendVersion(versions) {
      const fragment = document.createDocumentFragment();
      for (let i in versions) {
        const o = document.createElement("option");
        o.innerText = versions[i];
        o.value = versions[i];
        fragment.appendChild(o);
      }
      versionsSlot.appendChild(fragment);
    }

    function appendBuild(builds) {
      const fragment = document.createDocumentFragment();
      for (let i in builds) {
        const o = document.createElement("option");
        o.innerText = builds[i]['build'];
        o.value = builds[i]['build'];
        fragment.appendChild(o);
      }
      buildsSlot.appendChild(fragment);
    }

    async function getAllVersion() {
      const data = await fetch("https://api.papermc.io/v2/projects/paper");
      if (data.ok) {
        const { versions } = await data.json();
        return versions.reverse();
      } else {
        return null;
      }
    }

    async function getBuilds(version) {
      const data = await fetch(`https://api.papermc.io/v2/projects/paper/versions/${version}/builds`);
      if (data.ok) {
        const { builds } = await data.json();
        return builds.reverse();
      } else {
        return null;
      }
    }

    async function getJar(version, build) {
      const fileName = `paper-${version}-${build}.jar`;
      try {
        const data = await fetch(`https://api.papermc.io/v2/projects/paper/versions/${version}/builds/${build}/downloads/${fileName}`);
        if (data.ok) {
          statusText.innerText = `正在下载文件 ${data.url}`;
          const b = await data.blob();
          const jar = new File([b], fileName, {
            type: "application/java-archive"
          });

          const link = URL.createObjectURL(jar);
          const a = document.createElement("a");
          a.download = fileName;
          a.href = link;
          a.click();
        } else {
          console.log(`status: ${data.status}, statusText: ${data.statusText}`);
          alert("请求失败");
        }
      } catch (e) {
        console.error(e);
        alert("请求失败");
      }
    }

    init();
  })();
</script>

</html>

不用忍受paper那时不时卡死的swagger了

莹夜 ·

jvm http代理

java -DproxySet=true -DproxyHost=127.0.0.1 -DproxyPort=10809 -jar xxx.jar
莹夜 ·

提取字幕

ffmpeg -i in.mkv -an -vn -scodec copy -map 0:s:x out.ass

0:s:x的x为第几个字幕,数字从0开始。
输出的字幕格式需要根据视频内的来指定,有ass,srt,sup等等…

莹夜 ·

openssh-server升级后无法使用ssh-rsa私钥进行登录解决方案:
往/etc/ssh/sshd_config文件的最后追加这一行

PubkeyAcceptedKeyTypes=+ssh-rsa

然后再重启ssh服务

莹夜 ·

图片规格批量处理

<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>图片规格批量压缩</title>
  <script src="https://unpkg.com/jszip@3.10.1/dist/jszip.min.js"></script>
  <style>
    #machining_root {
      padding-top: 20px;
      text-align: center;
    }

    #machining_root input {
      width: 150px;
    }

    #machining_root #compress {
      width: 50px;
      outline: none;
      border-width: 1px;
      border-style: none none solid none;

      font-size: 16px;
      text-align: center;
    }
  </style>
</head>

<body>
  <div id="machining_root">
    <div style="margin-bottom: 6px;">
      <label for="compress">图片质量保留</label>
      <input id="compress" type="text" value="90" />
      <span>%</span>
    </div>
    <input type="file" accept="image/*" multiple="true" onchange="handlerFile(event)" />
    <p class="filename"></p>
    <p class="progress"></p>
  </div>
</body>

<script type="text/javascript">
  // QQ发图允许的最大图片像素数: 20249108: 3638 * 5566
  // 但忘记怎么试出来的了
  const maxRectSize = 2560 * 1440;
  // 允许的最大宽度是4500
  const maxRectWidth = 4500;
  // 允许的最大高度是10000
  const maxRectHeight = 9000;
  /** @type {Array<HTMLInputElement>} */
  const [compress, filename] = [document.querySelector("#machining_root #compress"), document.querySelector("#machining_root .filename")];
  const progress = document.querySelector("#machining_root .progress");

  async function handlerFile(e) {
    const compressVal = parseFloat(compress.value);
    if (isNaN(compressVal)) {
      progress.innerText = `compress: ${compress.value} 不是一个有效的数字`;
      return;
    }

    e.target.setAttribute("disabled", true);

    /** @type {Array<File>} */
    const files = Array.from(e.target.files);

    if (files.length <= 0) {
      e.target.removeAttribute("disabled");
      return;
    }

    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d");

    const zip = new JSZip();

    for (let i = 0; i < files.length; i++) {
      const file = files[i];
      filename.innerText = "正在处理: " + file.name;
      progress.innerText = `处理进度: ${i + 1}/${files.length}`;

      const img = new Image();
      img.src = URL.createObjectURL(file);
      await new Promise(resolve => {
        img.onload = () => {
          resolve();
        }
      });

      const [imgWidth, imgHeight] = getReasonableSize(getReasonablePixels(img));

      canvas.width = imgWidth;
      canvas.height = imgHeight;

      ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
      const blobLink = await new Promise(resolve => {
        canvas.toBlob(resolve, "image/jpeg", 0.01 * compressVal);
      });

      const fn = file.name.substring(0, file.name.lastIndexOf(".")) + ".jpg";
      zip.file(fn, new File([blobLink], fn));
    }

    filename.innerText = "";
    progress.innerText = "";

    zip.generateAsync({ type: "blob" })
      .then((zipBlob) => {
        const aTag = document.createElement("a");
        aTag.download = Date.now() + ".zip";
        aTag.href = URL.createObjectURL(zipBlob);
        aTag.click();
        e.target.removeAttribute("disabled");
      });
  }

  /**
   * 获取不超过QQ发送图片像素上限的分辨率
   * @param {HTMLImageElement} r
   * @returns [number, number]
   */
  function getReasonablePixels(r) {
    let _p = 1;
    if (r.width * r.height > maxRectSize) {
      while ((r.width * _p) * (r.height * _p) > maxRectSize) {
        _p -= 0.01;
      }
      return [r.width * _p, r.height * _p];
    } else {
      return [r.width, r.height];
    }
  }

  /**
   * 获取宽不超过4500,高不超过10000的分辨率
   * @param {[number, number]} r
   * @returns [number, number]
   */
  function getReasonableSize(r) {
    if (r[0] > maxRectWidth || r[1] > maxRectHeight) {
      let _p = 1;
      if (r[0] > r[1]) {
        _p = (maxRectWidth / r[0]).toFixed(3);
        r[0] = maxRectWidth;
        r[1] = r[1] * _p;
      } else {
        _p = (maxRectHeight / r[1]).toFixed(3);
        r[1] = maxRectHeight;
        r[0] = r[0] * _p;
      }
    }
    return r;
  }
</script>

</html>