「NASync」タグアーカイブ

ポート開放が要らないVPN

背景

自宅回線をMoneyForwd 光に変更した結果、v6Plus接続になりダイナミックDNS等でのVPN接続は難しくなったしまったので、別の方法を探していました。

やりたいこと

  • 自宅のHEMSなどにiPhoneからアクセスしたい
  • UGREEN NASyncのdockerコンテナで実現したい

TailScale

探した結果、TailScaleというサービスがあることを知りました。イメージとしてはクラウド上にあるルーターにVPN接続したい機器がVPNを張りに行ってそこでルーティングしてもらう感じですね。なので、OUT方向が通れば固定IPやポート開放ができなくてもOKです。

TailScaleはPersonalプランという無償プランがあります。 3 Users 100 Devicesまで無料というなんとも太っ腹なサービス! 素晴らしい(笑)

早速やってみる

  1. iPhoneでTailScalアプリをダウンロード
  2. 新規にアカウント作成してVPN接続!
  3. PCからTailScalの管理ページに入りSettings -> Keysで Auth KeysをGenerate Auth keyをポチっとと押してキーを払い出しコピーしておく
  4. UGREEN NASyncでdockerアプリからプロジェクト作成で適当なプロジェクト名(vpnなど)と保存パス(docker様にアサインした共有フォルダがいいでしょう)を指定して、以下の内容をCompose設定に記載して今すぐデプロイ!
version: "3.8"

services:
  tailscale:
    image: tailscale/tailscale:latest
    container_name: tailscale
    hostname: nasync
    network_mode: "host"
    privileged: true
    volumes:
      - /var/lib:/var/lib
      - /dev/net/tun:/dev/net/tun
    environment:
      # Tailscale 管理画面で作成した AuthKey を入れる
      - TS_AUTHKEY=tskey-auth-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
      # LAN 全体 (192.168.1.0/24) を iPhone に広告
      - TS_EXTRA_ARGS=--advertise-routes=192.168.1.0/24
    restart: unless-stopped
  1. TailScaleの管理ページからMachinesを見ると、iPhoneとnasyncが繋がってるはず!nasyncの…からEdit route settingsを選んで目的のサブネットにチェックを入れて保存すればそのサブネットに行けるようになります。

Machines Edit Route Settings

超簡単で素晴らしい!(^_^)/

QNAP TS-230 to UGREEN DXP2800 番外編

前回の記事で書いていたHEIC写真とJPGファイルの重複を何とかできないものかとNASyncの重複写真・類似写真検索やPCツールのDupFileEliminatorなどを試した見たのですが、検出されなかったので、chatGPTに相談した結果、何度かやり取りをした結果スクリプトが完成しました(^_^)/

ただし、もっと簡単な方法はUGREENが取ったバックアップからQNAP QuMagieでバックアップしていた日以前のHEICファイルを消してしまうことです。そうすれば、それ以前のデータはJPGで残っているわけですが重複は発生しません。まぁ、これが一番楽だと思います。

今回は、HEICとJPGの同じ写真を同じ写真として区別できるのかをテーマに実験してみました。

Pythonを使ったプログラムなのですが、Windows版Pythonだと HEICの処理がうまくいかなかったのでWSLのUBUNTU 22.04上で実行しました。

一発で目的の物が出てくることは稀ですがうまくいかない場合はエラー内容などをChatGPTに伝えれば対策を教えてくれます。素晴らしい。

方針

  1. 方針 HEICと JPG をハッシュ比較1して同じ写真を検出する
  2. 比較して同じ写真のペアを作る
  3. 確認用にファイルをコピー
  4. 人が確認して2問題なければ削除実施3

脚注

  1. フォーマットが違うのでハッシュ比較するために一旦、どちらの画像もPIL.Imageという形式に変換してハッシュを取っているようです。 ↩︎
  2. 比較用に./compare_outputフォルダにcompare.htmlと対象写真のコピーを作成してくれる仕様なんですが、写真が大きいので大量に読み込むとブラウザが固まってしまって使いもにならなかったので比較はExplorerで特大アイコンモードで見比べました。1つも間違ってませんでした。素晴らしい。 ↩︎
  3. 削除は比較スクリプトが吐き出したmatched_files.txtというリストをもとに削除します。 ↩︎
比較スクリプト
import os
from PIL import Image
import pyheif
import imagehash
from pathlib import Path
import shutil

# === 設定 ===
HEIC_ROOT = Path("<HEICファイルのパス>")
JPG_ROOT = Path("<JPGファイルのパス>")
OUTPUT_DIR = Path("compare_output")
MATCH_LIST_FILE = Path("matched_files.txt")
HASH_FUNC = imagehash.phash

# === 準備 ===
OUTPUT_DIR.mkdir(exist_ok=True)
heic_hashes = dict()
matched_pairs = []
heic_total = 0
jpg_total = 0
heic_errors = 0
jpg_errors = 0

print("🔍 HEICファイルをスキャン中...")
#print(f"📁 HEICルート: {HEIC_ROOT}")
#if not HEIC_ROOT.exists():
#    print("❌ 指定したHEICフォルダが存在しません。パスを確認してください。")
#    exit(1)
#else:
#    print("📂 HEICフォルダ一覧:")
#    for path in HEIC_ROOT.rglob("*"):
#        print("   ", path)

#for heic_file in HEIC_ROOT.rglob("*.heic"):
for ext in ["*.heic", "*.HEIC"]:
    for heic_file in HEIC_ROOT.rglob(ext):
        try:
            heif = pyheif.read(heic_file)
            image = Image.frombytes(heif.mode, heif.size, heif.data, "raw", heif.mode)
            h = str(HASH_FUNC(image))
            heic_hashes[h] = heic_file
            heic_total += 1
        except Exception as e:
            print(f"⚠️ HEICエラー: {heic_file} → {e}")
            heic_errors += 1

print(f"📸 HEIC読み込み成功: {heic_total} 件 / エラー: {heic_errors} 件")

print("📋 JPGファイルと照合中...")
for ext in ["*.jpg", "*.JPG"]:
    for jpg_file in JPG_ROOT.rglob(ext):
        try:
            with Image.open(jpg_file) as image:
                h = str(HASH_FUNC(image))
                if h in heic_hashes:
                    matched_pairs.append((jpg_file, heic_hashes[h]))
                jpg_total += 1
        except Exception as e:
            print(f"⚠️ JPGエラー: {jpg_file} → {e}")
            jpg_errors += 1

print(f"📸 JPG読み込み成功: {jpg_total} 件 / エラー: {jpg_errors} 件")
print(f"🔗 一致したペア数: {len(matched_pairs)}")

# === HTMLと画像出力 ===
html_lines = [
    "<html><head><meta charset='utf-8'>",
    "<style>body{font-family:sans-serif;} img{max-width:300px; margin:5px;} .pair{margin-bottom:40px;}</style>",
    "</head><body><h1>HEIC vs JPG 比較結果</h1>"
]

with open(MATCH_LIST_FILE, "w", encoding="utf-8") as f:
    for i, (jpg_path, heic_path) in enumerate(matched_pairs):
        base = f"pair_{i}"
        jpg_out = OUTPUT_DIR / f"{base}_jpg.jpg"
        heic_out = OUTPUT_DIR / f"{base}_heic.jpg"

        try:
            shutil.copy2(jpg_path, jpg_out)

            heif = pyheif.read(heic_path)
            heic_img = Image.frombytes(heif.mode, heif.size, heif.data, "raw", heif.mode)
            heic_img.save(heic_out, "JPEG")

            html_lines.append(f"<div class='pair'><h3>{base}</h3>")
            html_lines.append(f"<p><b>JPG:</b> {jpg_path}<br><b>HEIC:</b> {heic_path}</p>")
            html_lines.append(f"<img src='{jpg_out.name}' alt='JPG'>")
            html_lines.append(f"<img src='{heic_out.name}' alt='HEIC'>")
            html_lines.append("</div>")

            f.write(str(jpg_path) + "\n")
        except Exception as e:
            print(f"⚠️ 出力失敗: {jpg_path} / {heic_path} → {e}")

html_lines.append("</body></html>")
with open(OUTPUT_DIR / "compare.html", "w", encoding="utf-8") as f:
    f.write("\n".join(html_lines))

print(f"✅ 完了!{len(matched_pairs)} ペアを {OUTPUT_DIR}/ に保存しました。")
print("🖼 `compare_output/compare.html` をブラウザで開いて確認してください。")
print(f"📝 削除対象候補リスト: {MATCH_LIST_FILE}")

削除スクリプト

from pathlib import Path

MATCH_LIST_FILE = Path("matched_files.txt")
deleted = 0

if not MATCH_LIST_FILE.exists():
    print("❌ matched_files.txt が見つかりません。先に比較スクリプトを実行してください。")
    exit(1)

with open(MATCH_LIST_FILE, "r", encoding="utf-8") as f:
    jpg_paths = [Path(line.strip()) for line in f if line.strip()]

print(f"🗑 削除対象 JPG 数: {len(jpg_paths)}")
for jpg_path in jpg_paths:
    if jpg_path.exists():
        try:
            jpg_path.unlink()
            print(f"✅ 削除: {jpg_path}")
            deleted += 1
        except Exception as e:
            print(f"⚠️ 削除失敗: {jpg_path} → {e}")
    else:
        print(f"⚠️ ファイル存在しない: {jpg_path}")

print(f"✅ 削除完了: {deleted} 件")

まとめ

Python書けない私ですがChatGPTを使えば実用的なものができてしまうという素晴らしいですね。ただし、HEICとJPGではアルゴリズムの違いにより復元したRAWデータに差異が出てしまい完全には拾いきれず、結構違う写真として判断されてしまってるものが残ってしまいます。これは中々難しい問題です。まぁ、実用的には撮影情報からその日以前のHEICファイルを消した方が速いと思います(あるいは逆の方法でJPGを)

蛇足

何枚かHEICファイルじゃないってエラーが出たので調べてみたのですが、iPhoneの画像ファイルで.HEICなのにJPGなものがあるようです。iPhoneのBUG?

$ file IMG_xxxx.HEIC: ISO Media, HEIF Image HEVC Main or Main Still Picture Profile

$file IMG_yyyy.HEIC: JPEG image data, JFIF standard 1.01, aspect ratio, density 72×72, segment length 16, Exif Standard: [TIFF image data, big-endian, direntries=12, manufacturer=Apple, model=iPhone 12, xresolution=174, yresolution=182, resolutionunit=2, software=16.6, datetime=2023:09:11 15:33:46, hostcomputer=iPhone 12], baseline, precision 8, 4032×3024, components 3

さらに蛇足ですが、Pythonってループとかの構造を{}で囲ったりせず、インデントで判断するんですね。。。新鮮でした。インデントずれてたらエラーになったのでビビりました。

QNAP TS-230 to UGREEN DXP2800 ポチリ編

中華の誘惑に勝てませんでした。。。とりあえず。今の思いを綴りつつ、商品が届くのを待ちたいと思います。

1.背景

今までずっとQNAPを乗り継いできて機能的には全く不満はなかったのですが、最近、古いフィルム写真をデジタル化していておりまして、GooglePhotosの容量を増やさないといけなくなりました。。。それでもよかったのですが、今後も増え続けるわけでここらで、一旦、脱Googleとまではいかないまでも無償プランに戻してみようかと。。。

やってみたのですが、TS-230写真のインデックス化、サムネイル作成、人物判別、もの判別、重複判別が遅い!

というか、サムネイル作成なんかが遅いのは我慢できるのですが、それが走ってる間、他のことが何もできないくらい重くなるんですよね。これはあかん。

これを解決すべく、TS-213Gに上げようかとも思ったのですが、遅い原因がディスクIOな気もしていてその場合、キャッシュSSDを入れないとどうにもならない気もします。その場合、TS-264になるわけですが、高い! NASに10万は出したくないわ。

と迷っていたところ、UGREENのDXP2800がクラファンで爆売れ。と聞いてまぁ、興味は持っていました。

が、中華製いやだな。電池やケーブルならまだしも。NASウェアを中華製で大丈夫か?とスゲー不安だったのと、Youtuberの絶賛ぷりにちょっとおなか一杯な感じでした。NASの時代が来たー的な。

でもね。この手のNASは10年前からQNAPが出してて、最近はSynologyやAsustorとかも出てきて別に特別じゃないよね。QNAPの凄いところはアプリでしょ。と思ってたんですが、

TS-264レベルのハードDXP2800が5万円で買える!

しかも、普通に写真のAI認識とかもあるらしいぞ!?(精度はいまいちとも聞くけどそれはQNAPのQuMagieも大差ない)。自宅外アクセスもできるし、仮想マシンもDockerコンテナも動くらしいぞ!?

もぅ。これはポチっちゃうよね。(笑)

正直、仮想マシン動くならなんでもありじゃん。メモリも16GB積めるみたいだし。これ、もうNAS用PCだよね。(笑)

2.比較

QNAP TS-264UGREEN NASync DXP2800
CPUIntel Celeron N5105 (クアッドコア, 最大2.9GHz)Intel Celeron N5105 (クアッドコア, 最大2.9GHz)
RAM8GB DDR4 (最大16GB拡張可能)8GB DDR4 (最大16GB拡張可能)
ネットワーク2 x 2.5GbE (Intelチップ) + PCIeスロットで10GbE拡張可2 x 2.5GbE (Intelチップ)
M.2 NVMe2スロット (キャッシュ、ボリューム用)2スロット (キャッシュ、ボリューム用)
その他ポートUSB 3.2 Gen2 x2, USB 2.0 x2, HDMIUSB 3.2 Gen2 x2, USB 2.0 x1, HDMI
人物判別QuMagie (Edge TPUで高速化可)1AI Photo Album (今後の改善に期待)
S3バックアップネイティブ対応Docker経由での実現は可能だが、ネイティブ対応は要確認
Docker対応 (Container Station)対応
スナップショット対応対応 (Btrfs)2
RAID構成変更対応対応3
現時点での最安値69,980円443,263円
  1. TS-264は5000円くらいのGoogleのTPUを利用できてそれを入れるとAI処理がかなり早くなるらしい。ちょっと魅かれたけど。。。 ↩︎
  2. Btrfsで構築すればSnapShot取れるっぽい。 ↩︎
  3. 移行の都合上、8TBのHDDが3本しかなくて、1本余ってるHDDをDXPにさして構築したのち動作確認が終わったらTS-230のディスクを移植したいと思っている。故にシングルHDD→RAID1の変更は必須。 ↩︎
  4. 2025/7/6時点の価格。価格ドットコムで見てみたら旧NTT-Xで意外と安く売ってた。DXP2800の方はUGREENの直販サイトで値引きキャンペーン後の価格。 ↩︎

3.気になるところ

  • OS、アプリの完成度はまだまだって話もあるけど、どうなんでしょうねぇ。
  • 前面排気なんだよね。。。まぁ、机の上に置くわけでは二のでいいのだけれど。背面排気のがよかったな。
  • 私だけかもしれないですが、直販サイトで購入するとき、2000円割引のクーポンコードが発行されるのですが、製品選んでGooglePayでの支払いに進みとコード入力画面ないで決済に進んじゃったんですよね。。。サポートにお願いしてキャンセルして再購入したのですが、それでも入力画面出ませんでした。ただ、たまたま、決済エラーが出て決済実行画面に戻ったのでそこでコードを入れられたという。。。なんんだったんでしょうね。。。