diff --git a/tools/calendar_plan.py b/tools/calendar_plan.py
index 66b6e2a..d79753a 100644
--- a/tools/calendar_plan.py
+++ b/tools/calendar_plan.py
@@ -17,7 +17,7 @@
 予定の見分け方（key）。予定の説明の最後の行に「p2m-key: {key}」を入れる
   x:{id} / linkedin:{id}            予定表の1行（id は schedule.json の id）
   yt:{youtube_id}                   YouTube の公開
-  li-video:{youtube_id}             LinkedIn に動画を手で上げる（動画は LinkedIn に直接アップロードする決まりのため）
+  li-video:{youtube_id}             （2026-10-30 まで）LinkedIn に動画を手で上げる。今は本編も自動のリンク投稿なので作らない
 """
 import argparse, glob, json, os, re
 from datetime import datetime, timedelta, timezone
@@ -98,10 +98,7 @@ def main():
         watch = f"https://www.youtube.com/watch?v={yid}"
         title = m.get("youtube_title") or m.get("title", "")
         out.append(ev(f"yt:{yid}", f"YouTube公開 {name}", t, f"{title}\n{watch}"))
-        note = "YouTube の公開と同じ時刻。LinkedIn には動画を直接アップロードする決まり（自動では出ない）。"
-        if kind == "video":
-            note += f"文は iCloud の 動画/{m.get('title', '')}/投稿文_X_LinkedIn.txt"
-        out.append(ev(f"li-video:{yid}", f"LinkedIn 動画を上げる（手で）{name}", t, f"{note}\nYouTube: {watch}"))
+        # 本編の LinkedIn は 2026-10-30 から YouTube のリンクのカードで自動（予定表の linkedin:{id} で出る）。手で上げる予定は入れない
     out.sort(key=lambda x: (x["startTime"], x["key"]))
     print(json.dumps(out, ensure_ascii=False, indent=1))
 
diff --git a/tools/sns_autopost.py b/tools/sns_autopost.py
index 65ac978..8d31b59 100644
--- a/tools/sns_autopost.py
+++ b/tools/sns_autopost.py
@@ -94,7 +94,9 @@ def texts_for(entry, src):
         "text": posts.get("linkedin" + sfx, "").strip(),
         "comment": posts.get("linkedin_comment" + sfx, "").strip(),
         "url": meta.get("url" + sfx) or meta.get("url", ""),
-        "title": meta.get("title" + sfx) or meta.get("title", ""),
+        # YouTube の本編（2026-10-30 から LinkedIn にもリンクで出す）はカードの題を YouTube のタイトルにする
+        "title": (meta.get("youtube_title") if meta.get("kind") == "youtube_video" else None)
+                 or meta.get("title" + sfx) or meta.get("title", ""),
         "description": meta.get("meta_description" + sfx) or meta.get("meta_description", ""),
     }
 
@@ -258,7 +260,17 @@ def og_image(page_url):
 def li_thumbnail(token, person, page_url):
     """記事の表紙画像を LinkedIn に上げて image の URN を返す。失敗したら None（表紙なしで投稿する）。"""
     try:
-        img = og_image(page_url)
+        m = re.search(r"(?:youtu\.be/|youtube\.com/(?:watch\?v=|shorts/))([\w-]{6,})", page_url)
+        if m:   # YouTube の動画は、ページを読まずにサムネイルを直接取る（同意の画面などで og:image が取れないことがあるため）
+            img = f"https://i.ytimg.com/vi/{m.group(1)}/hqdefault.jpg"
+            for size in ("maxresdefault", "hqdefault"):
+                try:
+                    http("GET", f"https://i.ytimg.com/vi/{m.group(1)}/{size}.jpg", where="YouTube のサムネイル")
+                    img = f"https://i.ytimg.com/vi/{m.group(1)}/{size}.jpg"; break
+                except Exception:
+                    continue
+        else:
+            img = og_image(page_url)
         if not img:
             return None
         _, _, data = http("GET", img, where="表紙画像")
diff --git a/tools/sync_youtube_shorts.py b/tools/sync_youtube_shorts.py
index ae536f9..79d98aa 100644
--- a/tools/sync_youtube_shorts.py
+++ b/tools/sync_youtube_shorts.py
@@ -17,7 +17,9 @@
      12:30 の枠（2026-10-25 に足した）は「それ以外」に入り、13:00 になる
   3. まだ投稿していない予約は、YouTube 側で時刻を変えていたら合わせて直す
   4. 公開の時刻を過ぎていて、まだ予定表にないものは入れない（古いものを今さら出さない）
-LinkedIn には入れない（動画は LinkedIn に直接アップロードする決まり。video_rules.md の 0節の22）。
+本編は LinkedIn にも入れる（2026-10-30 エイイチの指示「LinkedIn は YouTube の投稿に統一」）。posts.linkedin_template があれば、
+YouTube の公開の時刻に LinkedIn の予約（id は VIDEO_{名前}-linkedin）を入れ、{url} を動画の URL にして posts.linkedin と
+posts.linkedin_comment を作る。LinkedIn では動画の URL のリンクのカード（YouTube のサムネイルつき）で出る。ショートは LinkedIn に出さない。
 
 使い方: python3 tools/sync_youtube_shorts.py --videos youtube_videos.json [--dry-run]
 """
@@ -100,6 +102,31 @@ def main():
         elif entry["post_at"] != post_at:
             changes.append(f"時刻を直した {sid} {entry['post_at']} → {post_at}")
             entry["post_at"] = post_at
+        # 本編の LinkedIn（2026-10-30 から）: YouTube の公開と同じ時刻。同じ時刻の LinkedIn の予約があれば30分ずつ後ろへ
+        if is_main and posts.get("linkedin_template"):
+            lid = sid[:-len("-x")] + "-linkedin"
+            lentry = next((e for e in sched["posts"] if e["id"] == lid), None)
+            if not (lentry and lid in done_ids) and (lentry or when >= now):
+                ltext = posts["linkedin_template"].replace("{url}", url)
+                lcom = posts.get("linkedin_comment_template", "").replace("{url}", url)
+                if posts.get("linkedin") != ltext or posts.get("linkedin_comment", "") != lcom:
+                    posts["linkedin"] = ltext
+                    if lcom:
+                        posts["linkedin_comment"] = lcom
+                    if not a.dry_run:
+                        with open(path, "w", encoding="utf-8") as f:
+                            json.dump(src, f, ensure_ascii=False, indent=2); f.write("\n")
+                ltaken = {e["post_at"] for e in sched["posts"] if e["platform"] == "linkedin" and e["id"] != lid}
+                lt = when.astimezone(JST)
+                while lt.isoformat() in ltaken:
+                    lt = lt + timedelta(minutes=30)
+                lpost = lt.isoformat()
+                if lentry is None:
+                    sched["posts"].append({"id": lid, "source": os.path.relpath(path, ROOT), "platform": "linkedin", "post_at": lpost})
+                    changes.append(f"足した {lid} {lpost} {v['title'][:30]}")
+                elif lentry["post_at"] != lpost:
+                    changes.append(f"時刻を直した {lid} {lentry['post_at']} → {lpost}")
+                    lentry["post_at"] = lpost
     sched["posts"].sort(key=lambda e: (e["post_at"], e["id"]))
     if changes and not a.dry_run:
         with open(SCHEDULE, "w", encoding="utf-8") as f:
