Search

2025/12/16

データSIMが使えるようになるまで1時間くらいかかるときもある


50GBのデータSIMカードをamazonで購入。
設定どおりにAndroid、iPhoneに挿して、10分20分待っても通信できず。。。
やり方わからずに発売元にも聞いてみた。
説明をしたりと、だいたい1時間くらい経過したらいつのまにか通信できるようになってた。
10分くらい待つ、とは聞いたことあったけど電波弱いと時間が必要かもと思います。

TORA様のSIMは通信速度も良かったです。
50GBも使えます。

eSIMと物理SIMがあるけど、端末やスマホを替えるかもしれないなら
物理SIMのほうが便利。
一つの端末で使い切りならeSIMの方が便利で少し安い。
30GBで3000円しないなら、たくさん使いそうなときだけeSIMで使えばいいのかもと思う。


360日で50GBが5,000円しないなら、サブ端末はこういうのでいいかも。
あとはWIFIを使うなど。
5000/12 =417円/月。



[

2025/12/12

【2025年12月版】WindowsでEasyOCRとPopplerを利用してPDFをOCRした文章の誤字脱字や改行を修正手順

前の続きです。

OCR処理したので文章が間違っていたりします。
主に誤字脱字と改行場所の修正が必要です。
解決策>languagetoolを使って修正する。 https://languagetool.org/
細かいところは直らないけど、ざっくり直る。
コードは下に。
JAVAが必要になりますのでインストールするか、zip解凍してパスを通す。
(Java17JREだとそれなりの容量です)





"""
OCR後の日本語テキストをローカルで整形・校正するパイプライン
- 前処理: 文字正規化 (neologdn) / 改行の再構成(OCR由来の不自然な改行を抑制)
- 校正: LanguageTool (ja-JP) を使った「安全寄り」自動修正
- 付帯: 修正候補ログ(JSONL) / 差分HTML生成

"""

from __future__ import annotations

import argparse
import html
import json
import os
import re
import sys
import time
from dataclasses import dataclass
from pathlib import Path
from typing import List, Tuple, Dict, Iterable

import neologdn
import regex  # pip: regex
from tqdm import tqdm
from rapidfuzz.distance import Levenshtein

import language_tool_python


# ----------------------------
# 設定
# ----------------------------

@dataclass
class Config:
    langtool_lang: str = "ja-JP"

    # 改行整形の方針(安全寄り)
    # - 行末が句点等で終わらない場合は、次の行を連結しやすい(OCRの行折返し対策)
    join_if_line_not_end_with: str = r"[。..!!\??]$"

    # ページ区切り行(OCR出力でよくある例)
    keep_page_markers: bool = True
    page_marker_regex: str = r"^\s*(=+)\s*Page\s*\d+\s*(=+)\s*$"

    # LanguageTool適用を安全寄りにするルール
    max_auto_apply_per_sentence: int = 6  # 1文あたり自動適用し過ぎない
    max_repl_len: int = 12                # 置換候補が長すぎるものは自動適用しない
    max_edit_distance: int = 3            # 元語と候補の編集距離が大きいものは自動適用しない
    min_token_len_for_apply: int = 1

    # 置換対象がこれにマッチする場合はスキップ(URLやコードっぽいもの、数値列など)
    skip_pattern: re.Pattern = re.compile(
        r"("
        r"https?://\S+|"
        r"www\.\S+|"
        r"[A-Za-z0-9_\-]{20,}|"          # 長い英数字(ハッシュ等)
        r"[\d]{8,}|"                    # 長い数字列
        r"`[^`]+`|"                     # インラインコード
        r")"
    )

    # 文分割(雑に句点で切ると壊れるので、まず段落→簡易文分割)
    sentence_split_regex: re.Pattern = re.compile(r"(?<=[。!?\?!])\s*")


# ----------------------------
# 文字正規化
# ----------------------------

def normalize_text(s: str) -> str:
    # neologdn: 全半角・連続記号・濁点分離などを良い感じに正規化
    s = neologdn.normalize(s)
    # いくつか追加の軽い正規化
    s = s.replace("\u00A0", " ")  # NBSP
    s = re.sub(r"[ \t]+", " ", s)
    return s


# ----------------------------
# 改行整形(OCRの行折返し対策)
# ----------------------------

def fix_linebreaks(text: str, cfg: Config) -> str:
    """
    OCR出力でありがちな「文中で改行されている」問題を緩和する。
    方針:
    - 空行は段落区切りとして維持
    - ページマーカー行は維持(設定で)
    - それ以外の行は、行末が句点/終端記号で終わらない場合は次行と連結
    """
    lines = text.splitlines()
    out: List[str] = []

    def is_page_marker(line: str) -> bool:
        return cfg.keep_page_markers and re.match(cfg.page_marker_regex, line) is not None

    buffer = ""

    for line in lines:
        raw = line.rstrip("\n")
        stripped = raw.strip()

        if is_page_marker(raw):
            # flush buffer
            if buffer:
                out.append(buffer.strip())
                buffer = ""
            out.append(raw)
            continue

        if stripped == "":
            # 空行 = 段落区切り
            if buffer:
                out.append(buffer.strip())
                buffer = ""
            out.append("")  # keep blank line
            continue

        # 連結対象判定
        if not buffer:
            buffer = stripped
        else:
            # 前行末が終端記号で終わらない → スペースで連結
            if re.search(cfg.join_if_line_not_end_with, buffer) is None:
                # 日本語では半角スペースより「そのまま連結」が自然な場合も多いが、
                # OCR後は単語境界が曖昧なのでスペース挟みで安全寄り
                buffer = buffer + " " + stripped
            else:
                # 文末っぽいなら段落内でも改行を段落内改行として維持(ただしここは好み)
                buffer = buffer + "\n" + stripped

    if buffer:
        out.append(buffer.strip())

    # 空行が多すぎる場合の軽い圧縮
    result = "\n".join(out)
    result = re.sub(r"\n{3,}", "\n\n", result)
    return result


# ----------------------------
# 文・段落の分割
# ----------------------------

def split_into_paragraphs(text: str, cfg: Config) -> List[str]:
    # ページマーカーは段落として独立扱い
    paras: List[str] = []
    buf: List[str] = []

    for line in text.splitlines():
        if cfg.keep_page_markers and re.match(cfg.page_marker_regex, line):
            if buf:
                paras.append("\n".join(buf).strip())
                buf = []
            paras.append(line.strip())
            continue

        if line.strip() == "":
            if buf:
                paras.append("\n".join(buf).strip())
                buf = []
            paras.append("")  # paragraph separator
        else:
            buf.append(line)

    if buf:
        paras.append("\n".join(buf).strip())

    # 空段落連続を抑制
    cleaned: List[str] = []
    prev_blank = False
    for p in paras:
        if p == "":
            if not prev_blank:
                cleaned.append("")
            prev_blank = True
        else:
            cleaned.append(p)
            prev_blank = False
    return cleaned


def split_paragraph_into_sentences(p: str, cfg: Config) -> List[str]:
    if p == "" or (cfg.keep_page_markers and re.match(cfg.page_marker_regex, p)):
        return [p]
    # 改行は文分割の妨げになるのでスペースへ(段落内の改行を保持したい場合は調整)
    tmp = p.replace("\n", " ")
    tmp = re.sub(r"\s{2,}", " ", tmp).strip()
    if not tmp:
        return [""]
    sents = [s.strip() for s in cfg.sentence_split_regex.split(tmp) if s.strip()]
    return sents if sents else [tmp]


# ----------------------------
# LanguageTool: 安全寄り自動適用
# ----------------------------

def apply_matches_safely(text: str, matches, cfg: Config) -> Tuple[str, List[Dict]]:
    """
    LanguageToolのmatchesを受け取り、安全寄りに自動適用する。
    - URL/コード/長い数字列などはスキップ
    - replacementが1個のみ & 短い & 編集距離が小さいものを優先
    - オフセットがずれるので、後ろから適用
    """
    logs: List[Dict] = []

    # 1文あたりの過剰適用抑制(matchesは文単位で呼ぶので単純に制限)
    applied = 0

    # offsetを使うので後ろから
    for m in sorted(matches, key=lambda x: x.offset, reverse=True):
        if applied >= cfg.max_auto_apply_per_sentence:
            break

        start = m.offset

        # 互換対応
        length = getattr(m, "errorLength", None)
        if length is None:
            length = m.error_length
        end = start + length

        frag = text[start:end]

        # スキップ条件
        if not frag or len(frag) < cfg.min_token_len_for_apply:
            continue
        if cfg.skip_pattern.search(frag):
            continue

        # 候補が無いならスキップ
        repls = list(m.replacements) if m.replacements else []
        if len(repls) != 1:
            # 候補が複数ある場合は自動適用しない(安全寄り)
            logs.append({
                "action": "skip_multi_candidates",
                "offset": start,
                "length": m.errorLength,
                "from": frag,
                "candidates": repls[:10],
                "ruleId": getattr(m, "ruleId", None),
                "message": getattr(m, "message", None),
            })
            continue

        repl = repls[0]
        if not repl or len(repl) > cfg.max_repl_len:
            logs.append({
                "action": "skip_long_candidate",
                "offset": start,
                "length": m.errorLength,
                "from": frag,
                "candidate": repl,
                "ruleId": getattr(m, "ruleId", None),
                "message": getattr(m, "message", None),
            })
            continue

        # 編集距離が大きいと誤訂正リスクが高い
        dist = Levenshtein.distance(frag, repl)
        if dist > cfg.max_edit_distance:
            logs.append({
                "action": "skip_far_edit",
                "offset": start,
                "length": m.errorLength,
                "from": frag,
                "candidate": repl,
                "edit_distance": dist,
                "ruleId": getattr(m, "ruleId", None),
                "message": getattr(m, "message", None),
            })
            continue

        # 適用
        new_text = text[:start] + repl + text[end:]
        logs.append({
            "action": "apply",
            "offset": start,
            "length": m.errorLength,
            "from": frag,
            "to": repl,
            "edit_distance": dist,
            "ruleId": getattr(m, "ruleId", None),
            "message": getattr(m, "message", None),
        })
        text = new_text
        applied += 1

    return text, logs


def proofread_text_with_languagetool(clean_text: str, cfg: Config) -> Tuple[str, List[Dict]]:
    """
    段落→文単位でLanguageToolを適用し、校正済みテキストとログを返す
    """
    tool = language_tool_python.LanguageTool(cfg.langtool_lang)

    paras = split_into_paragraphs(clean_text, cfg)

    out_paras: List[str] = []
    all_logs: List[Dict] = []

    for p in tqdm(paras, desc="LanguageTool proofreading", unit="para"):
        if p == "":
            out_paras.append("")
            continue
        if cfg.keep_page_markers and re.match(cfg.page_marker_regex, p):
            out_paras.append(p)
            continue

        sents = split_paragraph_into_sentences(p, cfg)
        fixed_sents: List[str] = []

        for s in sents:
            if not s:
                continue
            # 文単位でチェック(大量テキストでも安定)
            matches = tool.check(s)
            new_s, logs = apply_matches_safely(s, matches, cfg)

            # 文脈を壊しやすい過剰スペースを軽く整形
            new_s = re.sub(r"\s{2,}", " ", new_s).strip()

            # ログに文情報を付与
            for lg in logs:
                lg["sentence"] = s
                lg["sentence_fixed"] = new_s
            all_logs.extend(logs)

            fixed_sents.append(new_s)

        # 文間はスペースで連結(段落内の自然さ優先)
        out_paras.append(" ".join(fixed_sents).strip())

    result = "\n".join(out_paras)
    result = re.sub(r"\n{3,}", "\n\n", result)
    return result, all_logs


# ----------------------------
# 差分HTML
# ----------------------------

def make_diff_html(before: str, after: str, title: str) -> str:
    """
    最低限の差分表示(行単位)をHTMLで出す
    ※ もっと強力な差分が欲しければ difflib.HtmlDiff でもOK
    """
    import difflib

    before_lines = before.splitlines()
    after_lines = after.splitlines()

    diff = difflib.HtmlDiff(tabsize=2, wrapcolumn=120)
    html_body = diff.make_file(before_lines, after_lines, fromdesc="before", todesc="after", context=True, numlines=3)
    # タイトルだけ差し替え
    html_body = html_body.replace("", f"{html.escape(title)}")
    return html_body


# ----------------------------
# 入出力
# ----------------------------

def read_text(path: Path) -> str:
    # まずUTF-8、失敗ならCP932を試す(OCR出力でありがち)
    data = path.read_bytes()
    for enc in ("utf-8", "utf-8-sig", "cp932"):
        try:
            return data.decode(enc)
        except UnicodeDecodeError:
            continue
    # 最後はreplaceで
    return data.decode("utf-8", errors="replace")


def write_text(path: Path, text: str) -> None:
    path.write_text(text, encoding="utf-8", newline="\n")


def write_jsonl(path: Path, items: List[Dict]) -> None:
    with path.open("w", encoding="utf-8", newline="\n") as f:
        for it in items:
            f.write(json.dumps(it, ensure_ascii=False) + "\n")


def iter_input_txt(input_path: Path) -> List[Path]:
    if input_path.is_file():
        return [input_path]
    return sorted([p for p in input_path.rglob("*.txt") if p.is_file()])


# ----------------------------
# main
# ----------------------------

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--input", required=True, help="入力 .txt ファイル、または .txt を含むフォルダ")
    parser.add_argument("--outdir", required=True, help="出力フォルダ")
    parser.add_argument("--no-proofread", action="store_true", help="前処理のみ(LanguageTool校正なし)")
    parser.add_argument("--keep-page-markers", action="store_true", help="Pageマーカー行を維持する")
    args = parser.parse_args()

    cfg = Config(keep_page_markers=bool(args.keep_page_markers))

    in_path = Path(args.input)
    outdir = Path(args.outdir)
    outdir.mkdir(parents=True, exist_ok=True)

    inputs = iter_input_txt(in_path)
    if not inputs:
        print("入力 .txt が見つかりませんでした。", file=sys.stderr)
        sys.exit(1)

    for p in inputs:
        base = p.stem

        raw = read_text(p)

        # 1) 正規化
        norm = normalize_text(raw)

        # 2) 改行整形(OCR折返し対策)
        clean = fix_linebreaks(norm, cfg)

        clean_path = outdir / f"{base}_clean.txt"
        write_text(clean_path, clean)

        if args.no_proofread:
            continue

        # 3) LanguageTool 校正
        proof, logs = proofread_text_with_languagetool(clean, cfg)

        proof_path = outdir / f"{base}_proofread.txt"
        write_text(proof_path, proof)

        # 4) ログ
        log_path = outdir / f"{base}_suggestions.jsonl"
        write_jsonl(log_path, logs)

        # 5) 差分HTML
        diff_html = make_diff_html(clean, proof, title=f"diff: {base}")
        diff_path = outdir / f"{base}_diff.html"
        write_text(diff_path, diff_html)

        print(f"[OK] {p.name}")
        print(f"  clean : {clean_path}")
        print(f"  proof : {proof_path}")
        print(f"  log   : {log_path}")
        print(f"  diff  : {diff_path}")

    print("完了しました。")


if __name__ == "__main__":
    main()
[

【2025年12月版】Windows で EasyOCR と Poppler を利用して PDF を OCR する手順


【2025年12月版】Windows で EasyOCR と Poppler を利用して PDF を OCR する手順

画像だけのPDFファイルの画像を日本語のOCRにする方法をまとめてみました。
過去のPDFだとテキストでなく、画像形式のものも多い。
PDF>PNG化>OCR>テキスト化>読み上げソフト
で、読み上げでPDFファイルを聞きたいとき用です。
テスト的なのでどのくらい使えるかわからないです。
(お試しなので、メモとして記録)

スマホの読み上げでもいいし、こういうので車でも聞けるのが便利






以下では、Windows 環境で EasyOCR を動作させ、PDF ファイルをテキスト化するまでの流れを、順序立てて説明します。
Python 仮想環境の構築、Poppler のインストール、PDF の画像化、OCR スクリプトの作成と実行までを一通り解説します。


1. 作業ディレクトリへ移動する

まず、PowerShell を起動し、作業用のディレクトリへ移動します。

cd C:\Users\ユーザー名\Documents

※「ユーザー名」の部分は、ご自身の Windows のユーザー名に置き換えてください。


2. 仮想環境(venv)を作成し、有効化する

EasyOCR 用に独立した Python 環境(仮想環境)を作成します。これにより、他の Python プロジェクトやシステム全体の環境を汚さずにライブラリを管理できます。

python -m venv easyocr-env
.\easyocr-env\Scripts\activate

プロンプトに (easyocr-env) が表示されれば、有効化されています。


3. PyTorch(CPU版)のインストール

EasyOCR が内部で使用する機械学習ライブラリである PyTorch をインストールします。GPU を使用しない場合は、CPU 版で問題ありません。

pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu

4. EasyOCR のインストール

続いて、OCR エンジン本体である EasyOCR をインストールします。

pip install easyocr

5. Poppler をインストールし、PATH を設定する

Python のライブラリ pdf2image を用いて PDF を画像に変換する際には、Poppler に含まれる pdfinfo.exe および pdftoppm.exe が必要となります。

5-1. Poppler のダウンロード

以下の URL から、Poppler for Windows の最新版 ZIP ファイルをダウンロードします。

https://github.com/oschwartz10612/poppler-windows/releases/

例:

poppler-24.02.0-0-x86_64.zip

5-2. Poppler の展開

ダウンロードした ZIP ファイルを、以下のように展開します。

C:\poppler

展開後、次のファイルが存在することを確認してください。

C:\poppler\bin\pdfinfo.exe
C:\poppler\bin\pdftoppm.exe

5-3. PATH へ Poppler の bin を追加する(PowerShell)

Poppler の bin フォルダへのパスを、ユーザー環境変数 PATH に追加します。PowerShell で以下を実行します。

$popplerPath = "C:\poppler\bin"
$currentPath = [System.Environment]::GetEnvironmentVariable("Path", "User")

if ($currentPath -notlike "*$popplerPath*") {
    $newPath = "$currentPath;$popplerPath"
    [System.Environment]::SetEnvironmentVariable("Path", $newPath, "User")
    Write-Host "Poppler path added to USER PATH."
} else {
    Write-Host "Poppler path is already in PATH."
}

その後、PowerShell を一度終了し、再度起動してください。
※Windowsの検索窓>環境変数を検索してクリック>ユーザーかシステムの「Path」選択で編集>「C:\poppler\bin」を追加が簡単なときもあります。

5-4. Poppler が動作しているか確認する

PowerShell 上で、次のいずれかのコマンドを実行します。

pdfinfo -v

または

pdftoppm -h

いずれかが実行され、バージョン情報やヘルプが表示されれば、Poppler のインストールと PATH 設定は正常に完了しています。


6. pdf2image と Pillow のインストール

PDF を画像へ変換するために、pdf2imagePillow をインストールします。

pip install pdf2image pillow

7. OCR 作業用のフォルダを作成する

OCR 対象の PDF ファイルやスクリプトを配置するためのフォルダを作成します。

C:\Users\ユーザー名\Documents\ocr-work

このディレクトリに、OCR 対象の PDF ファイル(例:sample.pdf)を保存します。


8. PDF から画像へ変換し、OCR を実行するスクリプトを作成する

先ほど作成した ocr-work フォルダ内に、pdf_ocr.py という名前のファイルを作成し、以下の内容で保存します。

from pdf2image import convert_from_path
from easyocr import Reader

# PDF のパス
pdf_path = r"C:\Users\ユーザー名\Documents\ocr-work\sample.pdf"

# OCR エンジンの準備
reader = Reader(['ja', 'en'], gpu=False)

# PDF を画像へ変換
pages = convert_from_path(pdf_path, dpi=300)

all_text = []

for i, page in enumerate(pages):
    img_path = f"page_{i+1}.png"
    page.save(img_path, "PNG")

    results = reader.readtext(img_path, detail=0)
    all_text.append("\n".join(results))

# テキストとして保存
with open("output.txt", "w", encoding="utf-8") as f:
    f.write("\n\n==== page break ====\n\n".join(all_text))

print("OCR 完了しました。output.txt を確認してください。")

上記の pdf_path 内のパスは、ご自身のユーザー名および PDF ファイル名に合わせて変更してください。


9. スクリプトの実行

PowerShell で以下のコマンドを実行します。

cd C:\Users\ユーザー名\Documents\ocr-work
..\easyocr-env\Scripts\activate
python pdf_ocr.py

同じフォルダ内に output.txt が生成され、OCR 結果のテキストが保存されます。


まとめ

本記事では、Windows 環境において EasyOCR を用いて PDF を OCR するために必要な設定と、スクリプト作成手順を解説しました。主な流れは以下の通りです。

  1. Python 仮想環境の構築
  2. PyTorch および EasyOCR のインストール
  3. Poppler の導入と PATH 設定
  4. pdf2image による PDF の画像化
  5. Python スクリプトによる OCR 実行とテキスト保存

これらの手順を順に実施することで、Python を用いた OCR 処理環境が完成します。必要に応じて、大量の PDF を一括処理するバッチスクリプトや、OCR 精度を向上させるための前処理スクリプトを追加で作成することも可能です。


#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
===========================================
PDF OCR スクリプト(EasyOCR 版)
===========================================

【使い方】
    python ocr_pdf_easyocr.py input.pdf output_dir [start_page] [end_page]

【例 1】PDF 全ページを OCR する
    python ocr_pdf_easyocr.py C:\work\input.pdf C:\work\ocr_out

【例 2】PDF の 1〜300 ページだけ OCR する
    python ocr_pdf_easyocr.py C:\work\input.pdf C:\work\ocr_out 1 300

【出力】
    ・page_0001.txt のような各ページの OCR 結果
    ・all_pages_combined.txt(全ページ結合)

事前準備(必要なライブラリ)
    pip install easyocr pdf2image pillow

※ Poppler をインストール済みで PATH が通っていること
※ Windows の場合 poppler の bin フォルダを PATH に追加
-------------------------------------------
"""


# ==========================
# ======= 設定(定数) ======
# ==========================

POPPLER_PATH = None            # Poppler の bin フォルダ。PATH が通っていれば None のまま
LANG_LIST = ['ja', 'en']       # OCR 言語リスト
DPI = 300                      # PDF → 画像変換の解像度(高いほど精度↑)
PAGE_START = 1                 # デフォルト開始ページ(引数があれば上書き)
PAGE_END = None                # デフォルト終了ページ(None = 最後まで)


# ==========================
# ========= import ==========
# ==========================

import os
import sys
from pathlib import Path
import numpy as np
from pdf2image import convert_from_path
import easyocr


# ==========================
# ======= メイン処理 =======
# ==========================

def ocr_pdf(pdf_path: Path, output_dir: Path,
            first_page: int, last_page: int | None):

    output_dir.mkdir(parents=True, exist_ok=True)

    print(f"[INFO] EasyOCR Reader 初期化中 (LANG={LANG_LIST})...")
    reader = easyocr.Reader(LANG_LIST)

    if last_page is None:
        last_page = 9999  # 最大値(最後のページまで試す)

    combined_text_lines: list[str] = []

    page_num = first_page
    while page_num <= last_page:
        print(f"[INFO] ページ {page_num} を画像化…")

        try:
            images = convert_from_path(
                pdf_path,
                dpi=DPI,
                first_page=page_num,
                last_page=page_num,
                poppler_path=POPPLER_PATH
            )
        except Exception as e:
            print(f"[INFO] ページ {page_num} は最終ページの可能性: {e}")
            break

        if not images:
            print(f"[INFO] ページ {page_num} の画像取得なし → 終了")
            break

        image = images[0]

        print(f"[INFO] ページ {page_num} を OCR 中…")
        result = reader.readtext(np.array(image), detail=0)
        page_text = "\n".join(result)

        # ページごとに保存
        page_txt_path = output_dir / f"page_{page_num:04d}.txt"
        page_txt_path.write_text(page_text, encoding="utf-8")
        print(f"[INFO] → {page_txt_path} に保存")

        # 結合用
        combined_text_lines.append(f"===== Page {page_num} =====")
        combined_text_lines.append(page_text)
        combined_text_lines.append("")

        page_num += 1

    # 全ページ結合ファイル
    combined_txt_path = output_dir / "all_pages_combined.txt"
    combined_txt_path.write_text("\n".join(combined_text_lines), encoding="utf-8")
    print(f"[INFO] 全ページ結合テキスト → {combined_txt_path}")


# ==========================
# ========= エントリ =========
# ==========================

def main():
    if len(sys.argv) < 3:
        print("使い方: python ocr_pdf_easyocr.py input.pdf output_dir [start_page] [end_page]")
        sys.exit(1)

    pdf_path = Path(sys.argv[1])
    output_dir = Path(sys.argv[2])

    first_page = int(sys.argv[3]) if len(sys.argv) >= 4 else PAGE_START
    last_page  = int(sys.argv[4]) if len(sys.argv) >= 5 else PAGE_END

    if not pdf_path.exists():
        print(f"[ERROR] PDF が見つかりません: {pdf_path}")
        sys.exit(1)

    ocr_pdf(pdf_path, output_dir, first_page, last_page)


if __name__ == "__main__":
    main()



2025/10/08

2025/10/10まで Xiaomi POCO F7 12G+256GBはアマゾンより楽天市場のほうがポイント分だけ安い、お得かもしれない

アマゾンの10/10までのセール
Xiaomi POCO F7 12GB+256GBが48,980円税込送料無料で安い。
POCOはシャオミにサブブランド。
コスパが良いことで知られている。

Xiaomi POCO X7 Pro 8GB+256GB:38,364円
Xiaomi POCO F7 12GB+256GB 48,980円
■勝手なおすすめ
とりあえず使えるサブスマホほしい:X7 Pro(メモリ8GB&Dimensity 8400-Ultra)
ちゃんと使えるメインなスマホ:F7 Pro(Snapdragon 8 Gen 3&メモリ12GB)
AI使いたい&コスパ良いスマホほしい:F7(メモリ12GB&Snapdragon 8 Gen 4)
(※F7無印がF7Proよりも上のSnapdragon 8 Gen 4なので欲しくなる。)

AI使うときにメモリが8GBと12GBだと使えるモデルが違うのでかなり変わってくる。
AIを期待するならメモリは容量大きいものをおすすめ。






がっちり使いたいならXiaomi 15T Pro 12GB+256GB
カメラはライカSummilux 5倍望遠カメラ搭載、MediaTek Dimensity 9400+搭載。
FeliCa対応、IP68防水防塵のフル機能。
109,800円でも他メーカーよりも安い。

もっとすごいXiaomi 15 Ultra 16GB+512GBもある。
ライカのカメラがすごい。というか普通にコンデジのカメラ。
Snapdragon 8 Elite Mobile Platformがすごい。
やたらとアフターサービスがすごい。
値段以上の満足感ありそう。


楽天市場にもシャオミストアあり。
もちろんXiaomi POCO F7 12GB+256GBもセールに。
そちらの値段は54,980円。
高いの・・・と思いきや6,000円OFFクーポンあり。
54980-6000=48980円、ということは同じ?
でも楽天カードや楽天モバイルならポイントが7%に。
48980円+3428ポイント

Xiaomiシリーズみて比較して方が良さそう。
これだけ幅広く展開しているがすごい。

Xiaomi POCO X7 Pro 8GB+256GB:38,364円
Xiaomi POCO F7 12GB+256GB 48,980円
Xiaomi POCO F7 Pro 12GB+256GB 64,980円
Xiaomi 15T Pro 12GB+256GB:109,800円
Xiaomi 15 Ultra 16GB+512GB:151,874円

Xiaomi POCO X7 Pro:MediaTek Dimensity 8400-Ultra
Xiaomi POCO F7 :Snapdragon 8 Gen 4
Xiaomi POCO F7 Pro:Snapdragon 8 Gen 3
Xiaomi 15T Pro:MediaTek Dimensity 9400+
Xiaomi 15 Ultra:Snapdragon 8 Elite Mobile Platform

2025/09/13

markdownとかテキストデータの同期 QOwnNotes+ownCloud obsidianの代替としても

当たり前でざっくりな話しですが「テキストデータで複数デバイスのファイル同期な使いやすいな」です。
(もちろんObsidianが一番使いやすいはず。使ってないけど。)

QOwnNotes+ownCloud 10( PHP) + ownCloud DesktopApp windows版で使っています。
PCからはQOwnNotesで、スマホはアプリやブラウザからで。
編集は主にPCから。スマホは見るだけや他スタッフとの情報共有で。
dropboxなども便利ですが、シンプルさだと自前クラウドがとても便利。
あれこれの機能は無いければ、必要最低限なところに使っていて落ち着く。

-------関係ない話し-----

すごく古い話しですが、PHPはマンモス本で学んだ懐かしい思い出が・・・。
PHP3とか4のときに。
更新はあったけど未だに便利に使えるのがすごい。

PHP最初の開発者のRasmusの名言が物語っているのかも。
プログラミングが嫌いだから誰かの作ったコードを再利用できる便利なツールを作った、みたいな。
https://en.wikiquote.org/wiki/Rasmus_Lerdorf

学ぶなら独習PHPがいいと思いますが、他言語を知っているならウェブの情報だけでもできそう。
(ベタな話しですが、本屋で「PHPの本を」と聞くと松下幸之助のPHPを教えてくれる。普通のPHPはこっち。)

・PHP PHP: Hypertext Preprocessor
・PHPとは、Peace and Happiness through Prosperity
--------------





QOwnNotes
ownCloud PHP版
conoha WINGで運用
※OwnClowdはPHP7系での動作なので注意を。

下記のような月額500円以下のサーバーを使って試してみるのもいいかも。
WebARENA Indigo®

限界があるけど、SSHが使えると共有サーバーでも使用範囲が広がるかも。
バリューサーバー
リトルサーバー

もちろん、さくら、X-server、ロリポップもSSH使えます。

[

2025/08/22

Nexcloud31のインストール Conoha Wing 共有サーバーでの自前クラウド構築

共有サーバーなので推奨ではありません。
Nextcloudをconoha Wingにインストールしてテスト動作
Nextcloud 31.0.7です。
Nextcloud https://nextcloud.com/


ウェブアクセスOK
iPhoneアプリからのアクセスもOK
共有サーバーなのでテスト的に。
本格運用は専用サーバーやVPSで。
OwncloudもNextcloudもPHP動作なので共有サーバーでも動く。

こういうの使わなくてもGoogleドライブもOneDriveもある。
Dropboxもある。
DS124/GやDS225+ /GでSynology Drive使えばDropboxみたいに使える。
ファイル共有というよりは、アクセス制限の軽い掲示板+αなら使えるのかもと思う。


DS225+ /G https://amzn.to/3VaAXw3
5.7万くらい

DS124/G https://amzn.to/3HJN9AY
2.5万くらい
※Synlogyだとlinux動いているのでNextcloudもインストールできる。

DropboxPlus 3年版 2TB  https://amzn.to/4fSZeAE
4.3万くらい ※5000ポイントあるので実質3.8万くらい

※価格は2025/08/22時点参考




Nextcloud インストール手順とトラブルシューティング

1. ZIPファイルのダウンロードと展開

Nextcloud公式インストールガイド

最新版のNextcloud ZIPファイル

curl -L https://download.nextcloud.com/server/releases/latest.zip -o nextcloud.zip
unzip nextcloud.zip -d /var/www/html/cloud

2. ファイルの整理とパーミッション設定

展開された`nextcloud`フォルダ内のすべてのファイルを`/var/www/html/cloud`ディレクトリに移動します。また、不要なファイルやフォルダを削除し、適切なパーミッションを設定します。

mv nextcloud/* cloud
mv nextcloud/.htaccess cloud
mv nextcloud/.reuse cloud
mv nextcloud/.user.ini cloud
rm -rf nextcloud nextcloud-10.0.10.zip

3. ファイルの所有者とグループ設定

`apache`ユーザーと`abc`グループに、`/var/www/html/cloud`ディレクトリ内のすべてのファイルとディレクトリの所有権を変更し、グループ書き込み権限を付与します。

chown -R apache:abc /var/www/html/cloud
chmod -R g+w /var/www/html/cloud


ここでインストールしたURLにアクセス
DBなどを設定して、ログインして正常動作しているのか確認する。
※DBアドレスは「localhost」がNGなので、IPアドレスで指定
※それ以外の設定はそのままでいけるはず。

使えるならここまででOK。
これ以降はnextcloudの管理者設定>概要のメッセージを見て必要に応じて対応する

4. リバースプロキシ設定

.htaccess に以下を追加(Nextcloud設置ディレクトリ直下)

一番下に追加

<IfModule mod_headers.c>
  RequestHeader set X-Forwarded-Proto "https"
  RequestHeader set X-Forwarded-Ssl "on"
</IfModule>

Nextcloud 側設定(config.php)

一番下に追加

'trusted_proxies' => ['127.0.0.1'], // WING 環境の localhost プロキシを信頼
'forwarded_for_headers' => ['HTTP_X_FORWARDED_FOR'],

5. PHP メモリ制限の設定

※これはphp.iniでの設定かも

.htaccess 一番下にに以下を追加

php_value memory_limit 512M
php_value upload_max_filesize 200M
php_value post_max_size 200M


6. HTTPSアクセス対応

iphoneアプリからアクセスできず。下記でOKになった。

◯安全ではないHTTP経由でのサイトへのアクセス。代わりにHTTPSを要求するようにサーバーを設定することを強くお勧めします。そうしないと「クリップボードにコピー」や「service workers」のような重要なウェブ機能が動作しません! 詳細については、ドキュメント↗を参照してください。

下記でiphoneアプリからOKになった。

Nextcloud 側の設定

config/config.php に追加:
'overwrite.cli.url' => 'https://yourdomain.com/nextcloud',
'overwriteprotocol' => 'https',

7. ブルートフォース対策制限の対応

◯あなたの接続アドレスが"1.2.3.4"として識別され、現在ブルートフォース対策制限が適用されているため、さまざまなリクエストのパフォーマンスが低下しています。接続アドレスがあなたのアドレスでない場合、プロキシが正しく設定されていない可能性があります。詳細については、ドキュメント↗を参照してください。

config.phpに追加

'trusted_proxies' => [
  '111.222.333.444',          // 表示されているプロキシIP
  '111.222.0.0/16'          // 共有環境でIPが変動するならCIDRで広めに(安全性とトレードオフ)
],
'forwarded_for_headers' => ['HTTP_X_FORWARDED_FOR','HTTP_X_REAL_IP'],

※'forwarded_for_headers' => ['HTTP_X_FORWARDED_FOR']をリバースプロキシでやっているなら重複するので調整する


2025/08/21

LM STUDIOで6GBのGPUでもそれなりに動くものテスト 

WindowsのLM STUDIOで動作テスト
GPUは6GBです。
2025/08/21にやりました。

Qwenの4Bモデルが低スペックでも動く情報多いのでやってみました。



1:Qwen3 4B 2507
https://huggingface.co/Qwen/Qwen3-4B

2:Jan V1 4B
https://huggingface.co/janhq/Jan-v1-4B

3:gemma-3n-e4b
https://huggingface.co/google/gemma-3n-E4B

■こんにちは、と聞いたり、文章書いてまとめてをお願いしたとき。

1:Qwen3 4B 2507
レスポンス早い。
文章いれてまとめてなど使えるレベル。
これなら使っていいかもと思う。

2:Jan V1 4B
レスポンス良いが、
必ずthinkingモードに入るのでパラメータ間違ったかも。
(Qwen3が元なので性能同じなの?)

3:gemma-3n-e4b
Qwen3と同じく。
とても早い。


■MCPにDuckDuckGoのAPIを設定したとき
今日の東京の天気は? の返答


1:Qwen3 4B 2507
レスポンス早い。
公開しているウェブを教えてくれただけ。

2:Jan V1 4B
たくさん検索し直して、回答短い。
パラメータの影響。

3:gemma-3-270m
回答得られず。
うまくいくときありので質問が悪い?




-------
qwen/qwen3-4b-2507
search
({"query":"今日の東京の天気","max_results":1}…})

mcp/ddg-search





今日の東京の天気については、日本気象協会のサイトで詳細な情報が見られます。

以下のリンクから、東京都の今日の天気(気温、降水確率、その他)を確認できます:

👉 


ただし、この回答では具体的な天気(例:晴れ、雨など)や気温の数値は提供できておりません。実際の天気を知るためには、上記のリンクをご確認ください。
-------

-------
jan-v1-4b
Thought for 2.94 seconds






search
({"query":"Tokyo today weather forecast","max_resul…})

mcp/ddg-search






Thought for 7.14 seconds















search
({"query":"Tokyo today weather forecast The Weather…})

mcp/ddg-search






Thought for 4.83 seconds










search
({"query":"Tokyo weather today forecast real time",…})

mcp/ddg-search






Thought for 3.70 seconds








search
({"query":"japanweather.org/tokyo real time weather…})

mcp/ddg-search





Thought for 4.17 seconds








search
({"query":"japanweather.org/tokyo current weather",…})

mcp/ddg-search





Thought for 4.40 seconds







search
({"query":"japanweather.org/tokyo today weather tem…})

mcp/ddg-search





Thought for 3.25 seconds







search
({"query":"japanweather.org/tokyo real time temp no…})

mcp/ddg-search





Thought for 2.43 seconds





search
({"query":"japanweather.org/tokyo now temperature",…})

mcp/ddg-search





Thought for 2.67 seconds







search
({"query":"japanweather.org/tokyo current temperatu…})

mcp/ddg-search





Thought for 3.50 seconds






search
({"query":"東京の現在の天気","max_results":1}…})

mcp/ddg-search





Thought for 3.82 seconds






search
({"query":"東京 天気 2023-10-05","max_results":1}…})

mcp/ddg-search





Thought for 4.00 seconds





search
({"query":"東京の現在の天気 2024-10-05","max_results":1}…})

mcp/ddg-search





Thought for 3.41 seconds





search
({"query":"東京 10月 天気予報","max_results":1}…})

mcp/ddg-search





Thought for 3.57 seconds






search
({"query":"2024-10-05 東京 天気","max_results":1}…})

mcp/ddg-search





Thought for 2.16 seconds




東京の今日の天気は、曇りで午前18度、午後23度と予想されます。気温差が大きいので、体調管理に注意してください。
-------

-------
google/gemma-3n-e4b
search
({"query":"今日の東京の天気"}…})

mcp/ddg-search




I'm sorry, I was unable to find the weather for today in Tokyo. The search query returned no results. I cannot fulfill your request at this time.
-------


「LM StudioのddgのMCPの設定方法」
Pythonがインストールしてある状態で下記をpowershellにて。

py -m pip install uv
py -m pip install uvx
py -m pip install duckduckgo-mcp-server
py -m duckduckgo_mcp_server


LM Studio → 右ペイン Program → Install > Edit mcp.json を開き、次を追加して保存:
{
  "mcpServers": {
    "ddg-search": {
      "command": "uvx",
      "args": ["duckduckgo-mcp-server"]
    }
  }
}

チャットの窓でddg-seachをチェックして青色にする




2025/06/25

owncloud10の共有サーバーへのインストール

詳細な設定はできないが簡単に使うならOwnCloudがある
細かい設定はできないがその分簡単に使えるのかも。
オンラインファイラーとして使うなら、WEBDAV機能を停止すればOK。
Nextcloudよりも低機能と思います。

バージョン10 php版について。
ownCloud Infinite Scaleとは別物です。

WEBDAVでqownnotesと一緒に使ってmarkdownの保存場所として便利かもと思います。
https://www.qownnotes.org/




インストール方法は下記投稿を参考にさせていただきました。
@_RubiLeah_ (るびりあ) 
ownCloudのインストールと設定


●SSHでログインしてインストールしたいディレクトリへ移動
※powershellとかでSSHログイン。

cd public_html
mkdir cloud


●wgetしてhttps://owncloud.com/download-server/のzipを展開
wget https://download.owncloud.com/server/stable/owncloud-complete-20250311.zip
unzip owncloud-complete-20250311.zip

●ファイルの整理

mv owncloud/* cloud
mv owncloud/.htaccess cloud
mv owncloud/.user.ini cloud

rm -rf owncloud owncloud-complete-20250311.zip

●ファイルの所有者とパーミッションを変更する。
chown -R user:group *
chmod -R g+w *

※user:group ユーザー名とグループ名
調べ方は下記コマンドなど
ps aux
ps -eo user,group,comm

●インストールしたURLへアクセスしてDB設定など

https://hoge.hoge/cloud