mcpField
S
show-gallery

show-gallery

github.com/thepexcelクリエイティブ・メディア40 回インストール
リポジトリ

show-gallery について

ローカルフォルダー内の画像や動画を閲覧するギャラリーを作成するスキルです。`gallery.html` を生成し、プレビューツールでページを開いて確認用のスクリーンショットを撮影します。

主な機能

  1. 01対象フォルダーに `gallery.html` を作成
  2. 02生成された画像や動画を閲覧
  3. 03メディアの拡大とパスのコピー
  4. 04パス未指定時にメディアフォルダーを一覧表示
  5. 05Preview でギャラリーを開きスクリーンショットを撮影

活用シーン

  1. 01出力フォルダーにある生成メディアの確認
  2. 02複数のフォルダーから画像や動画を探す
  3. 03後続作業用にメディアのパスをコピー

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/thepexcel/agent-skills --skill show-gallery

インストール後、Claude Code で/show-galleryを入力して読み込まれたか確認します。

詳細

/show-gallery — 汎用メディアブラウザー

任意のローカルフォルダーにある生成済みの画像と動画を閲覧します。クリックすると拡大表示され、パスをクリップボードにコピーできます。

クイックフロー

พี่ระ: "โชว์รูปใน output-fal/" หรือ "show gallery D:/path/to/folder"
→ สร้าง gallery.html ใน target folder
→ preview_start server ที่ serve folder นั้น
→ navigate to /gallery.html
→ screenshot ให้พี่ระดู

呼び出されたとき

Step 1: フォルダーを特定する
  • พี่ระがパスを指定した場合 → そのまま使用します
  • 指定がない場合 → どのフォルダーか尋ねるか、最後に生成したフォルダーを使用します
Step 2: gallery.html を作成する

下記の Gallery HTML Template を使用して、gallery.html をターゲットフォルダーに書き込みます。 GALLERY_ROOT を修正し、フォルダーの絶対パスにしてください(\ ではなく / を使用します)。

Step 3: プレビューを開く
  1. .claude/launch.json を確認し、このフォルダーを配信するサーバーがすでにあるか確認します
  2. なければ、新しい設定を追加します(次のポート番号など。例:8125、8126...)
  3. preview_start → preview_eval: window.location.href = '/gallery.html'
  4. 1~2 秒待ってから、preview_screenshot で พี่ระに見せます
Step 4: 操作

พี่ระがギャラリーページからパスをコピーしてチャットに貼り付け、画像や動画に何をするか伝えます。


Gallery HTML Template

下記のブロック全体をコピーし、ターゲットフォルダーに gallery.html として書き込みます GALLERY_ROOT の1行を修正します

<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<title>Gallery</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#1a1a1a;color:#fff;font-family:system-ui,sans-serif;padding:12px}
.header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;flex-wrap:wrap;gap:8px}
.header h1{font-size:14px;color:#aaa;font-weight:400}
.header .count{font-size:13px;color:#666}
.controls{display:flex;gap:6px;align-items:center}
.controls button{background:#333;border:1px solid #555;color:#ccc;padding:4px 10px;border-radius:4px;cursor:pointer;font-size:12px}
.controls button:hover{background:#444}
.controls button.active{background:#555;color:#fff;border-color:#888}
.grid{display:grid;gap:8px}
.grid.cols-2{grid-template-columns:repeat(2,1fr)}
.grid.cols-3{grid-template-columns:repeat(3,1fr)}
.grid.cols-4{grid-template-columns:repeat(4,1fr)}
.card{position:relative;border-radius:6px;overflow:hidden;background:#222;cursor:pointer}
.card:hover{outline:2px solid #ffd700}
.card img,.card video{width:100%;display:block;aspect-ratio:2/3;object-fit:cover}
.card .info{position:absolute;bottom:0;left:0;right:0;background:linear-gradient(transparent,rgba(0,0,0,.85));padding:6px 8px}
.card .fname{font-size:11px;color:#ddd;word-break:break-all}
.card .meta{font-size:10px;color:#888;margin-top:2px}
.card .copy-btn{position:absolute;top:6px;right:6px;background:rgba(0,0,0,.7);border:1px solid #555;color:#ccc;padding:3px 8px;border-radius:4px;font-size:11px;cursor:pointer;opacity:0;transition:opacity .2s}
.card:hover .copy-btn{opacity:1}
.card .copy-btn.copied{background:#2a5a2a;border-color:#4a4;color:#8f8}
.card .badge{position:absolute;top:6px;left:6px;background:rgba(0,0,0,.7);color:#ffd700;padding:2px 6px;border-radius:3px;font-size:10px;font-weight:700}
.modal{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.95);z-index:100;justify-content:center;align-items:center;flex-direction:column;cursor:pointer}
.modal.active{display:flex}
.modal img,.modal video{max-height:85vh;max-width:95vw;object-fit:contain;border-radius:6px}
.modal .path-bar{margin-top:8px;padding:6px 14px;background:#333;border-radius:4px;font-size:12px;color:#aaa;cursor:pointer;user-select:all}
.modal .path-bar:hover{background:#444;color:#fff}
.modal .caption{margin-top:6px;font-size:11px;color:#888;max-width:80vw;text-align:center;max-height:60px;overflow:auto}
.toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:#2a5a2a;color:#8f8;padding:8px 20px;border-radius:6px;font-size:13px;opacity:0;transition:opacity .3s;z-index:200;pointer-events:none}
.toast.show{opacity:1}
</style>
</head>
<body>

<div class="header">
  <h1 id="title">Gallery</h1>
  <span class="count" id="count"></span>
  <div class="controls">
    <button onclick="setCols(2)">2</button>
    <button onclick="setCols(3)" class="active">3</button>
    <button onclick="setCols(4)">4</button>
    <span style="color:#555">|</span>
    <button onclick="filterType('all')" class="active" id="btn-all">All</button>
    <button onclick="filterType('image')" id="btn-image">IMG</button>
    <button onclick="filterType('video')" id="btn-video">VID</button>
  </div>
</div>
<div class="grid cols-3" id="grid"></div>

<div class="modal" id="modal">
  <img id="modal-img" style="display:none">
  <video id="modal-vid" controls style="display:none"></video>
  <div class="path-bar" id="modal-path" onclick="event.stopPropagation();copyPath(this.dataset.path)"></div>
  <div class="caption" id="modal-caption"></div>
</div>

<div class="toast" id="toast">Copied!</div>

<script>
// ====== CONFIG — แก้ตรงนี้ ======
const GALLERY_ROOT = 'D:/ClaudeMediaGen/output-fal';
// =================================

const IMG_EXT = /\.(png|jpg|jpeg|webp|gif)$/i;
const VID_EXT = /\.(mp4|webm|mov)$/i;
let allItems = [];
let currentFilter = 'all';
const captions = {};

async function loadGallery() {
  const resp = await fetch('./');
  const html = await resp.text();
  const links = [...html.matchAll(/href="([^"]+)"/g)].map(m => decodeURIComponent(m[1]));
  const mediaFiles = links.filter(f => IMG_EXT.test(f) || VID_EXT.test(f)).sort();

  // Load .txt captions
  const txtFiles = links.filter(f => f.endsWith('.txt'));
  await Promise.all(txtFiles.map(async t => {
    try {
      const r = await fetch(t);
      captions[t.replace('.txt','')] = (await r.text()).slice(0, 200);
    } catch(e) {}
  }));

  allItems = mediaFiles.map(f => {
    const isVid = VID_EXT.test(f);
    const baseName = f.replace(/\.[^.]+$/, '');
    return { file: f, isVideo: isVid, caption: captions[baseName] || '' };
  });

  renderGrid();
}

function renderGrid() {
  const grid = document.getElementById('grid');
  const filtered = currentFilter === 'all' ? allItems
    : currentFilter === 'image' ? allItems.filter(i => !i.isVideo)
    : allItems.filter(i => i.isVideo);

  document.getElementById('count').textContent =
    `${filtered.length}/${allItems.length} items (${allItems.filter(i=>!i.isVideo).length} img, ${allItems.filter(i=>i.isVideo).length} vid)`;

  grid.innerHTML = '';
  filtered.forEach((item, i) => {
    const card = document.createElement('div');
    card.className = 'card';
    const absPath = GALLERY_ROOT + '/' + item.file;
    const sizeLabel = item.isVideo ? 'VIDEO' : '';

    if (item.isVideo) {
      card.innerHTML = `
        <video src="${item.file}" muted preload="metadata"></video>
        ${sizeLabel ? `<span class="badge">${sizeLabel}</span>` : ''}
        <button class="copy-btn" onclick="event.stopPropagation();copyPath('${absPath}',this)">Copy Path</button>
        <div class="info"><div class="fname">${item.file}</div></div>`;
    } else {
      card.innerHTML = `
        <img src="${item.file}" loading="lazy">
        <button class="copy-btn" onclick="event.stopPropagation();copyPath('${absPath}',this)">Copy Path</button>
        <div class="info"><div class="fname">${item.file}</div></div>`;
    }

    card.onclick = () => openModal(item, absPath);
    grid.appendChild(card);
  });

  if (filtered.length === 0) {
    grid.innerHTML = '<p style="grid-column:1/-1;text-align:center;color:#666;padding:40px">No media files found</p>';
  }
}

function openModal(item, absPath) {
  const modal = document.getElementById('modal');
  const mImg = document.getElementById('modal-img');
  const mVid = document.getElementById('modal-vid');
  const mPath = document.getElementById('modal-path');
  const mCap = document.getElementById('modal-caption');

  if (item.isVideo) {
    mImg.style.display = 'none';
    mVid.style.display = 'block';
    mVid.src = item.file;
    mVid.play();
  } else {
    mVid.style.display = 'none';
    mVid.pause();
    mImg.style.display = 'block';
    mImg.src = item.file;
  }

  mPath.textContent = absPath;
  mPath.dataset.path = absPath;
  mCap.textContent = item.caption;
  modal.classList.add('active');
  modal.onclick = (e) => {
    if (e.target === modal) { modal.classList.remove('active'); mVid.pause(); }
  };
}

function copyPath(path, btn) {
  navigator.clipboard.writeText(path).then(() => {
    showToast('Copied: ' + path);
    if (btn) { btn.textContent = 'Copied!'; btn.classList.add('copied'); setTimeout(() => { btn.textContent = 'Copy Path'; btn.classList.remove('copied'); }, 1500); }
  });
}

function showToast(msg) {
  const t = document.getElementById('toast');
  t.textContent = msg;
  t.classList.add('show');
  setTimeout(() => t.classList.remove('show'), 2000);
}

function setCols(n) {
  const grid = document.getElementById('grid');
  grid.className = 'grid cols-' + n;
  document.querySelectorAll('.controls button').forEach(b => {
    if (['2','3','4'].includes(b.textContent)) b.classList.toggle('active', b.textContent == n);
  });
}

function filterType(type) {
  currentFilter = type;
  ['all','image','video'].forEach(t => {
    document.getElementById('btn-'+t)?.classList.toggle('active', t === type);
  });
  renderGrid();
}

loadGallery();
</script>
</body>
</html>

プロンプトのルール(このスキルを呼び出す Claude 向け)

  1. ターゲットフォルダーにgallery.html を作成します — 上記のテンプレートをコピーします
  2. GALLERY_ROOT を修正し、フォルダーの絶対パスに合わせます(スラッシュを使用します)
  3. <title> を修正し、内容が伝わるようにします
  4. launch.json を確認します — このフォルダーを配信するサーバーがなければ、新しい設定を追加します
  5. preview_start → /gallery.html に移動 → preview_screenshot で พี่ระに見せます
  6. スクリーンショットがタイムアウトした場合は、代わりに preview_snapshot を試し、Preview panel で確認するよう พี่ระに伝えます

フォルダー閲覧モード

พี่ระがパスを指定せずに「browse folder」または「ดู folder」と言った場合:

  • メディアファイルを含む出力フォルダーの一覧を表示します
  • พี่ระにフォルダーを選んでもらいます
  • その後、通常のフローに従ってギャラリーを作成します

関連スキル

  • /gen-character-image — キャラクターを生成した後にギャラリーを使用します
  • /kie-ai — Kie.ai で生成した後にギャラリーを使用します
  • /fal-ai — fal.ai で生成した後にギャラリーを使用します
  • /comfyui-user — ComfyUI で生成した後にギャラリーを使用します
  • /image-analysis — ギャラリーで選択した画像を分析します

コメント