<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>imakat.com</title>
	<atom:link href="https://imakat.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://imakat.com</link>
	<description>工夫と改善で人生をちょっと豊かに</description>
	<lastBuildDate>Tue, 01 Sep 2026 21:08:09 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1</generator>

<image>
	<url>https://i0.wp.com/imakat.com/wp-content/uploads/2023/07/cropped-80d64ecd340db4e2ca3224859b04caed.png?fit=32%2C32&#038;ssl=1</url>
	<title>imakat.com</title>
	<link>https://imakat.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">160909258</site>	<item>
		<title>【Mac】「VTT字幕生成＆かんたんエディタ」を作った(無料)。</title>
		<link>https://imakat.com/2026/08/26/29722/</link>
		
		<dc:creator><![CDATA[imakat]]></dc:creator>
		<pubDate>Wed, 26 Aug 2026 06:59:23 +0000</pubDate>
				<category><![CDATA[字幕作成]]></category>
		<category><![CDATA[デジタル]]></category>
		<category><![CDATA[ものづくり]]></category>
		<category><![CDATA[マルチモーダル]]></category>
		<category><![CDATA[ターミナル]]></category>
		<category><![CDATA[vtt]]></category>
		<guid isPermaLink="false">https://imakat.com/?p=29722</guid>

					<description><![CDATA[１　はじめに&#160; 今回の投稿は、前回の投稿ID-29651「VTT字幕の全自動生成」とペアになる投稿です。 以下のビデオは、 前回の投稿である動画音声ファイルからVTT字幕を生成するパートと、今回の投稿のメインで [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">１　<strong>はじめに</strong>&nbsp;</h2>



<figure class="wp-block-image size-large is-resized"><a href="https://imakat.com/rd.php?id=4ijLOKVB.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=4ijLOKVB.png" alt="" style="width:308px;height:auto"/></a></figure>



<p class="wp-block-paragraph">今回の投稿は、前回の投稿<a href="https://imakat.com/2026/08/21/29651/" target="_blank">ID-29651</a>「VTT字幕の全自動生成」とペアになる投稿です。</p>



<p class="wp-block-paragraph">以下のビデオは、</p>



<p class="wp-block-paragraph">前回の投稿である動画音声ファイルからVTT字幕を生成するパートと、今回の投稿のメインであるエディタで編集するパートを一緒にしたものです。ご視聴ください。</p>


<div class="sc-dynamic-embed">
  <style>
  /* リンクの見た目を整える */
  .sc-dynamic-embed .sc-link-container { 
      display: flex; 
      margin-bottom: 10px; 
      flex-wrap: wrap; 
      align-items: center; 
  }
  .sc-dynamic-embed .sc-link { margin-bottom: 0; }
  .sc-dynamic-embed .sc-link a {
    font-size: 15px; 
    font-weight: normal;
    text-decoration: underline;
    color: #0073aa;
  }
  .sc-dynamic-embed .sc-link a:hover { text-decoration: none; color: #000; }
  
  @media (max-width: 500px) {
    .sc-dynamic-embed .sc-link a { font-size: 13px; }
  }

  .sc-dynamic-embed .imk-line {
      display: inline-block;
      width: 100%;
      border-radius: 2px;
      transition: background-color 0.1s;
  }

  .sc-dynamic-embed #subtitleOverlay,
  .sc-dynamic-embed #scSubtitleOverlay,
  .sc-dynamic-embed .overlay-cue,
  .sc-dynamic-embed .band {
      display: none !important;
      opacity: 0 !important;
      visibility: hidden !important;
      pointer-events: none !important;
  }
  </style>

  <div class="sc-link-container">
    <p class="sc-link">
      <a href="https://imakat.com/ds62/?drid=105" target="_blank"
         onclick="return scStopAndGo(event, this);">
        👉低画質・枠外字幕はこちら
      </a>
    </p>
  </div>

  <style>
            :root{ --dr5emd-max: 1920px; }
            #subtitleOverlay, #scSubtitleOverlay, .overlay-cue, .band { display: none !important; opacity: 0 !important; }
            .imk-line { display: inline-block; width: 100%; border-radius: 2px; transition: background-color 0.1s; }
            .dr5emd-sublist details > p{ height:200px; overflow:auto; background-color:#EDF7FF; padding:2px 6px; margin:0; box-shadow:3px 3px 4px black; position: relative; }
            .dr5emd-sublist details > summary{ padding:2px 6px; width:100%; background-color:#ddd; border:none; box-shadow:3px 3px 4px black; cursor:pointer; list-style:none; }
            
                .video-wrap{position:relative;width:100%;margin:0 auto}
                #myVideo{width:100%;height:auto;min-height:200px;display:block;background:#000;}</style><div class="dr5emd-container"><div class="video-wrap"><video id="myVideo" controls poster="https://imakat.com/rd.php?id=osG6NR1G.png" playsinline preload="none" style="width:100%;height:auto;">  <source src="https://imakat.com/rd.php?id=QaFjV0en.mp4" type="video/mp4">  <track src="https://imakat.com/rd.php?id=HjJNFEwi.vtt" label="日本語" srclang="ja" kind="subtitles"></video></div><div class="dr5emd-sublist"><details translate="yes"><summary translate="yes">字幕一覧(クリック)</summary> <p translate="yes">
(<a href="#" class="imk-cue" data-seek="0:10" translate="no">00:00:10</a>) 全自動VTT字幕作成&エディター起動。<br>
(<a href="#" class="imk-cue" data-seek="0:16" translate="no">00:00:16</a>) このアプリケーションの操作をしてみます。<br>
(<a href="#" class="imk-cue" data-seek="0:25" translate="no">00:00:25</a>) フォルダの中から対象となるファイル、<br>
(<a href="#" class="imk-cue" data-seek="0:30" translate="no">00:00:30</a>) 動画または音声ファイルを見つけます。<br>
(<a href="#" class="imk-cue" data-seek="0:36" translate="no">00:00:36</a>) ここに用意してあります。<br>
(<a href="#" class="imk-cue" data-seek="0:40" translate="no">00:00:40</a>) mp4を用意してあります。<br>
(<a href="#" class="imk-cue" data-seek="0:44" translate="no">00:00:44</a>) このとき私は、<br>
(<a href="#" class="imk-cue" data-seek="0:48" translate="no">00:00:48</a>) 1080pの動画はちょっと重いので、<br>
(<a href="#" class="imk-cue" data-seek="0:53" translate="no">00:00:53</a>) 音質はそのままにして、<br>
(<a href="#" class="imk-cue" data-seek="0:56" translate="no">00:00:56</a>) 画質を360pに圧縮して、<br>
(<a href="#" class="imk-cue" data-seek="1:00" translate="no">00:01:00</a>) そういうファイルを生成しておきます。これですね。<br>
(<a href="#" class="imk-cue" data-seek="1:06" translate="no">00:01:06</a>) あえて字幕作成はこの360pの方を使います。<br>
(<a href="#" class="imk-cue" data-seek="1:13" translate="no">00:01:13</a>) そうすることで処理の負荷が軽くなって、<br>
(<a href="#" class="imk-cue" data-seek="1:17" translate="no">00:01:17</a>) エラーのリスクも減ります。<br>
(<a href="#" class="imk-cue" data-seek="1:21" translate="no">00:01:21</a>) それでは、このファイルの上で右クリックします。<br>
(<a href="#" class="imk-cue" data-seek="1:26" translate="no">00:01:26</a>) クイックアクション。<br>
(<a href="#" class="imk-cue" data-seek="1:29" translate="no">00:01:29</a>) それであらかじめ作ってある、<br>
(<a href="#" class="imk-cue" data-seek="1:34" translate="no">00:01:34</a>) 全自動VTT字幕作成&エディター起動、これを行います。<br>
(<a href="#" class="imk-cue" data-seek="1:41" translate="no">00:01:41</a>) するとターミナルが開いて、<br>
(<a href="#" class="imk-cue" data-seek="1:45" translate="no">00:01:45</a>) 字幕作成の作業が開始します。<br>
(<a href="#" class="imk-cue" data-seek="1:57" translate="no">00:01:57</a>) こんな形でどんどん進んでいきます。<br>
(<a href="#" class="imk-cue" data-seek="2:02" translate="no">00:02:02</a>) 今1/3って出てますけども、<br>
(<a href="#" class="imk-cue" data-seek="2:05" translate="no">00:02:05</a>) これは11分ぐらいの動画になるので、<br>
(<a href="#" class="imk-cue" data-seek="2:09" translate="no">00:02:09</a>) 動画を3つに分割して処理が進んでいきます。<br>
(<a href="#" class="imk-cue" data-seek="2:17" translate="no">00:02:17</a>) ご覧のように、まず今1/3ですね。1番目を実行してます。<br>
(<a href="#" class="imk-cue" data-seek="2:25" translate="no">00:02:25</a>) どんどん進んでいきますけども、今まだちょっと時間がかかってますね。<br>
(<a href="#" class="imk-cue" data-seek="2:31" translate="no">00:02:31</a>) それで今1/3が済んで、<br>
(<a href="#" class="imk-cue" data-seek="2:35" translate="no">00:02:35</a>) 次の2/3に入っています。<br>
(<a href="#" class="imk-cue" data-seek="2:45" translate="no">00:02:45</a>) こうしてターミナルを表示しておきますと、<br>
(<a href="#" class="imk-cue" data-seek="2:49" translate="no">00:02:49</a>) 進捗が分かるので便利ですね。<br>
(<a href="#" class="imk-cue" data-seek="2:53" translate="no">00:02:53</a>) もしエラーになった時は、<br>
(<a href="#" class="imk-cue" data-seek="2:57" translate="no">00:02:57</a>) この画面の文字列をコピーして、<br>
(<a href="#" class="imk-cue" data-seek="3:00" translate="no">00:03:00</a>) ご自身の生成AIにペーストして質問するか、<br>
(<a href="#" class="imk-cue" data-seek="3:06" translate="no">00:03:06</a>) あるいは私のimakat図書館のチャットに、<br>
(<a href="#" class="imk-cue" data-seek="3:11" translate="no">00:03:11</a>) ペーストして質問してもらえれば、解決に向かえると思います。<br>
(<a href="#" class="imk-cue" data-seek="3:18" translate="no">00:03:18</a>) 今2/3ですね。<br>
(<a href="#" class="imk-cue" data-seek="3:22" translate="no">00:03:22</a>) これで今2/3が終わって、<br>
(<a href="#" class="imk-cue" data-seek="3:26" translate="no">00:03:26</a>) 最後の3/3に入ってます。<br>
(<a href="#" class="imk-cue" data-seek="3:35" translate="no">00:03:35</a>) 今のところまでは順調に来てますね。<br>
(<a href="#" class="imk-cue" data-seek="3:41" translate="no">00:03:41</a>) そんな形で処理が進んでいきます。<br>
(<a href="#" class="imk-cue" data-seek="3:45" translate="no">00:03:45</a>) 今3/3もそろそろ終わろうとしています。<br>
(<a href="#" class="imk-cue" data-seek="3:58" translate="no">00:03:58</a>) はい。<br>
(<a href="#" class="imk-cue" data-seek="4:06" translate="no">00:04:06</a>) 最終的なVTTを結合し、<br>
(<a href="#" class="imk-cue" data-seek="4:10" translate="no">00:04:10</a>) 連番も再度振り付けて、<br>
(<a href="#" class="imk-cue" data-seek="4:14" translate="no">00:04:14</a>) それでファイルが完成いたしました。<br>
(<a href="#" class="imk-cue" data-seek="4:18" translate="no">00:04:18</a>) ここでVTTファイルが作られて保存されてます。<br>
(<a href="#" class="imk-cue" data-seek="4:26" translate="no">00:04:26</a>) 見てみます。<br>
(<a href="#" class="imk-cue" data-seek="4:29" translate="no">00:04:29</a>) 字幕VTTファイルと<br>
(<a href="#" class="imk-cue" data-seek="4:32" translate="no">00:04:32</a>) 字幕一覧テキストファイルが生成されています。<br>
(<a href="#" class="imk-cue" data-seek="4:38" translate="no">00:04:38</a>) それで同時にですね、<br>
(<a href="#" class="imk-cue" data-seek="4:42" translate="no">00:04:42</a>) ここに字幕かんたんエディターが起動しました。<br>
(<a href="#" class="imk-cue" data-seek="4:51" translate="no">00:04:51</a>) これはですね、字幕かんたんエディターは操作が非常に簡単で、<br>
(<a href="#" class="imk-cue" data-seek="4:56" translate="no">00:04:56</a>) エディターの操作は見たままです。<br>
(<a href="#" class="imk-cue" data-seek="5:02" translate="no">00:05:02</a>) ちょっと動かしてみます。<br>
(<a href="#" class="imk-cue" data-seek="5:23" translate="no">00:05:23</a>) それでは始めます。<br>
(<a href="#" class="imk-cue" data-seek="5:26" translate="no">00:05:26</a>) この画像ですけれども、これは日本の山村によく見かける<br>
(<a href="#" class="imk-cue" data-seek="5:31" translate="no">00:05:31</a>) 棚田の風景です。<br>
(<a href="#" class="imk-cue" data-seek="5:37" translate="no">00:05:37</a>) コンピューターのデータの管理を...<br>
(<a href="#" class="imk-cue" data-seek="5:42" translate="no">00:05:42</a>) 新規に字幕を追加したい時は、<br>
(<a href="#" class="imk-cue" data-seek="5:46" translate="no">00:05:46</a>) この上のテキストボックスですね、ここに文字列を入れます。<br>
(<a href="#" class="imk-cue" data-seek="6:00" translate="no">00:06:00</a>) 今例えば、これは適当に入れてみますけども、<br>
(<a href="#" class="imk-cue" data-seek="6:04" translate="no">00:06:04</a>) こうやって入れますと<br>
(<a href="#" class="imk-cue" data-seek="6:08" translate="no">00:06:08</a>) こんなのが入るわけですね。それで<br>
(<a href="#" class="imk-cue" data-seek="6:18" translate="no">00:06:18</a>) 今4秒で入れましたけども、これを修正して<br>
(<a href="#" class="imk-cue" data-seek="6:22" translate="no">00:06:22</a>) 4秒ではなくて8秒ぐらいにすると。<br>
(<a href="#" class="imk-cue" data-seek="6:26" translate="no">00:06:26</a>) 8秒にしてみましょうか。そうすると<br>
(<a href="#" class="imk-cue" data-seek="6:43" translate="no">00:06:43</a>) 8秒で字幕の表示が切れましたね。<br>
(<a href="#" class="imk-cue" data-seek="6:51" translate="no">00:06:51</a>) それでは始めます。<br>
(<a href="#" class="imk-cue" data-seek="6:54" translate="no">00:06:54</a>) それでこのですね、音声の波形の表示は<br>
(<a href="#" class="imk-cue" data-seek="6:58" translate="no">00:06:58</a>) 拡大縮小ができます。<br>
(<a href="#" class="imk-cue" data-seek="7:04" translate="no">00:07:04</a>) 適当な位置でですね、シークバーを止めて<br>
(<a href="#" class="imk-cue" data-seek="7:08" translate="no">00:07:08</a>) そこからスペースキーを押すとですね、開始します。<br>
(<a href="#" class="imk-cue" data-seek="7:13" translate="no">00:07:13</a>) で、もう一回スペースキーを押すと止まります。<br>
(<a href="#" class="imk-cue" data-seek="7:19" translate="no">00:07:19</a>) こういった形で確認ができます。<br>
(<a href="#" class="imk-cue" data-seek="7:24" translate="no">00:07:24</a>) それで、まあ全体にですね、一度は通しで<br>
(<a href="#" class="imk-cue" data-seek="7:45" translate="no">00:07:45</a>) 一番多い修正はですね、字幕の終了時刻の調整です。<br>
(<a href="#" class="imk-cue" data-seek="7:49" translate="no">00:07:49</a>) まだ話してるうちにですね、字幕が消えるということがありますので<br>
(<a href="#" class="imk-cue" data-seek="7:58" translate="no">00:07:58</a>) この終了時刻をですね、調整・修正します。<br>
(<a href="#" class="imk-cue" data-seek="8:03" translate="no">00:08:03</a>) これはいいかな。例えば、<br>
(<a href="#" class="imk-cue" data-seek="8:06" translate="no">00:08:06</a>) 準アクティブなデータを置きます。<br>
(<a href="#" class="imk-cue" data-seek="8:08" translate="no">00:08:08</a>) 一番下がT2長期保管段。ここはアーカイブ領域です。<br>
(<a href="#" class="imk-cue" data-seek="8:13" translate="no">00:08:13</a>) 重要なのは、それぞれの段にサイズもメーカーも異なるバラバラのHDDを割り当てられる点です。<br>
(<a href="#" class="imk-cue" data-seek="8:20" translate="no">00:08:20</a>) ま、ここで止めて、これがちょうど終わってますが、これ4分2秒の位置ですね。<br>
(<a href="#" class="imk-cue" data-seek="8:28" translate="no">00:08:28</a>) ところが実際は、終了が4分2秒になってるから、<br>
(<a href="#" class="imk-cue" data-seek="8:32" translate="no">00:08:32</a>) これはいいですね。そのまま変える必要はないですね。<br>
(<a href="#" class="imk-cue" data-seek="8:38" translate="no">00:08:38</a>) です。これをソフトウェアで1つの巨大な階層構造として管理します。<br>
(<a href="#" class="imk-cue" data-seek="8:45" translate="no">00:08:45</a>) このシステムの革新にある設計思想…<br>
(<a href="#" class="imk-cue" data-seek="8:48" translate="no">00:08:48</a>) ま、こういった形でですね、中身をチェックしていきます。<br>
(<a href="#" class="imk-cue" data-seek="8:54" translate="no">00:08:54</a>) アプリの終了は、この左上のボタンを押します。<br>
(<a href="#" class="imk-cue" data-seek="9:00" translate="no">00:09:00</a>) そうするとですね、フォルダにですね、<br>
(<a href="#" class="imk-cue" data-seek="9:05" translate="no">00:09:05</a>) 字幕VTTファイルと字幕一覧テキストファイルがですね、できます。<br>
(<a href="#" class="imk-cue" data-seek="9:13" translate="no">00:09:13</a>) この出来上がった2つのファイルを配信用フォルダにですね、<br>
(<a href="#" class="imk-cue" data-seek="9:18" translate="no">00:09:18</a>) コピペすれば完了ということになります。<br>
(<a href="#" class="imk-cue" data-seek="9:25" translate="no">00:09:25</a>) 以上です。<br>
</p> </details>
<style>
/* 汎用ベーススタイル（マイライブラリ等の単独表示・ブログ埋め込み用） */
details { font: 16px "Open Sans", Calibri, sans-serif; width: 100%; }
details > summary { padding: 2px 6px; width: 100%; background-color: #ddd; border: none; box-shadow: 3px 3px 4px black; cursor: pointer; list-style: none; }
details > p { font: 14px "Open Sans", Calibri, sans-serif; height:150px; overflow: scroll; background-color: #EDF7FF; padding: 2px 6px; margin: 0; box-shadow: 3px 3px 4px black; }

/* 動画パッケージ（mu1emd, dr5, dr6等）内での上書き専用スタイル */
.mu1emd-sublist details > p, .dr5-sublist details > p, .dr6-sublist details > p {
    display: block;
    min-height: 180px;
    max-height: 240px;
    overflow-y: auto;
    background-color: #EDF7FF;
    padding: 6px 8px;
    margin: 0;
    box-shadow: 3px 3px 4px black;
    border-radius: 0 0 5px 5px;
}
</style>
</div></div>            <script>
            document.addEventListener("DOMContentLoaded", function(){
                var video = document.getElementById("myVideo");
                if(!video) return;

                            });
            </script>
            

  <script>
  (function(){
    var me = document.currentScript;
    var wrapper = me ? me.closest('.sc-dynamic-embed') : null;

    function protectVideo() {
      var target = wrapper ? wrapper : document;
      var mediaEls = target.querySelectorAll('video, audio');
      mediaEls.forEach(function(v){
        if(v.dataset.protected === 'true') return;
        v.dataset.protected = 'true';
        v.setAttribute('controlsList', 'nodownload');
        v.oncontextmenu = function() { return false; };
        v.addEventListener('contextmenu', function(e){ e.preventDefault(); return false; }, false);
      });
    }

    function initSubtitles() {
      var target = wrapper ? wrapper : document;
      var video = target.querySelector('video, audio');
      var listContainer = target.querySelector('details > p');
      
      if (!video) return false; 
      
      if (video.dataset.subInit === 'true') return true; 
      video.dataset.subInit = 'true';

      var oldOverlay = target.querySelector('#subtitleOverlay') || target.querySelector('#scSubtitleOverlay');
      if (oldOverlay) {
          oldOverlay.style.setProperty('display', 'none', 'important');
          oldOverlay.innerHTML = ''; 
      }

      function isSpecialMode() {
        var isFs = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement);
        var isPiP = !!(document.pictureInPictureElement && document.pictureInPictureElement === video) || (video.webkitPresentationMode === "picture-in-picture");
        var isIOSFs = !!video.webkitDisplayingFullscreen; 
        return isFs || isPiP || isIOSFs;
      }

      // 初期状態のセット（リストがなければ最初から showing に）
      try {
        if(video.textTracks && video.textTracks.length > 0){
          for(var i=0; i<video.textTracks.length; i++){
             if(video.textTracks[i].kind === 'subtitles' || video.textTracks[i].kind === 'captions'){
                 video.textTracks[i].mode = (!listContainer) ? "showing" : "hidden";
             }
          }
        }
      } catch(e){}

      // 字幕一覧が存在する場合のみ、リストのフォーマット処理を行う
      if (listContainer) {
          var detailsEl = target.querySelector("details");
          if (detailsEl) {
              detailsEl.open = true; 
              var summaryEl = detailsEl.querySelector("summary");
              if (summaryEl) summaryEl.textContent = "字幕(シーン)はここをクリック";
          }

          if (!listContainer.dataset.formatted) {
              var html = listContainer.innerHTML;
              var lines = html.split(/<br\s*\/?>/i);
              var newHtml = "";
              for(var j=0; j<lines.length; j++) {
                  if(lines[j].trim() === "") continue;
                  newHtml += "<span class='imk-line'>" + lines[j] + "</span><br>";
              }
              listContainer.innerHTML = newHtml;
              listContainer.dataset.formatted = "true";
          }

          var rootSublist = target.querySelector(".dr5-sublist") || listContainer.parentElement;
          if (rootSublist) {
            rootSublist.addEventListener("click", function(e){
              var a = e.target.closest && e.target.closest("a.imk-cue[data-seek]");
              if(!a) return;
              e.preventDefault();
              var ts = a.getAttribute("data-seek");
              if(!ts) return;
              var p = ts.trim().split(":").map(function(x){return parseInt(x,10)||0;});
              var sec = (p.length===2) ? p[0]*60+p[1] : (p.length===3 ? p[0]*3600+p[1]*60+p[2] : null);
              if(sec==null) return;
              try{ video.currentTime = sec; if(video.paused) video.play(); }catch(_){}
            });
          }
      }

      // ★ dr52.php と全く同じ「常時監視」のロジック
      // これにより、Safariが読み込み遅延を起こしても確実にONになります
      video.addEventListener("timeupdate", function(){
        var hasList = (listContainer !== null);
        var desiredMode = (isSpecialMode() || !hasList) ? "showing" : "hidden";
        
        try {
            if(video.textTracks && video.textTracks.length > 0){
                for(var i=0; i<video.textTracks.length; i++){
                    if((video.textTracks[i].kind === 'subtitles' || video.textTracks[i].kind === 'captions') && video.textTracks[i].mode !== desiredMode) {
                        video.textTracks[i].mode = desiredMode;
                    }
                }
            }
        } catch(e){}

        // リストがない場合はこれ以降のハイライト処理をスキップ
        if (!hasList) return;

        var cues = listContainer.querySelectorAll("a.imk-cue");
        if(cues.length === 0) return;
        var cur = video.currentTime;
        var activeA = null;

        for(var i=0; i<cues.length; i++){
            var ts = cues[i].getAttribute("data-seek");
            if(!ts) continue;
            var p = ts.trim().split(":").map(function(x){return parseInt(x,10)||0;});
            var t = (p.length===2) ? p[0]*60+p[1] : (p.length===3 ? p[0]*3600+p[1]*60+p[2] : null);
            if(t !== null && cur >= t - 0.5){ activeA = cues[i]; } 
            else if(t > cur){ break; }
        }

        if(activeA){
            var activeLine = activeA.closest(".imk-line");
            if(!activeLine) activeLine = activeA;

            if(activeLine.classList.contains("active-hl")) return;

            var allLines = listContainer.querySelectorAll(".imk-line");
            for(var k=0; k<allLines.length; k++) {
                allLines[k].classList.remove("active-hl");
                allLines[k].removeAttribute("style"); 
            }
            var allLinks = listContainer.querySelectorAll("a");
            for(var m=0; m<allLinks.length; m++) {
                allLinks[m].classList.remove("active-hl");
                allLinks[m].removeAttribute("style"); 
            }

            activeLine.classList.add("active-hl");
            activeLine.style.setProperty("background-color", "#ffff00", "important");
            activeLine.style.setProperty("color", "red", "important");
            activeLine.style.setProperty("font-weight", "normal", "important");
            
            var newLinks = activeLine.querySelectorAll("a");
            for(var n=0; n<newLinks.length; n++) {
                newLinks[n].style.setProperty("color", "red", "important");
                newLinks[n].style.setProperty("text-decoration", "none", "important");
            }

            if(listContainer.offsetParent !== null){
                var containerRect = listContainer.getBoundingClientRect();
                var activeRect = activeLine.getBoundingClientRect();
                var targetScroll = listContainer.scrollTop + (activeRect.top - containerRect.top) - (listContainer.clientHeight / 2) + (activeLine.clientHeight / 2);
                listContainer.scrollTo({ top: targetScroll, behavior: "smooth" });
            }
        }
      });

      return true;
    }

    var checks = 0;
    var checkTimer = setInterval(function(){
      protectVideo();
      var success = initSubtitles();
      checks++;
      if (success || checks > 20) { 
        clearInterval(checkTimer);
      }
    }, 500); 

    if (!window.scStopAndGo) {
      window.scStopAndGo = function(event, link){
        try{
          var mediaEls = document.querySelectorAll('video, audio');
          mediaEls.forEach(function(m){
            try{
              if (!m.paused) m.pause();
              if (document.pictureInPictureElement === m && document.exitPictureInPicture) {
                document.exitPictureInPicture().catch(function(){});
              }
            }catch(e){}
          });
        }finally{
          event.preventDefault();
          setTimeout(function(){
            if (link.target === '_blank') {
              window.open(link.href, '_blank');
            } else {
              window.location.href = link.href;
            }
          }, 50);
        }
        return false;
      };
    }
  })();
  </script>
</div>



<h2 class="wp-block-heading"><strong>２　</strong>ウインドウを統合したエディタを作った</h2>



<p class="wp-block-paragraph"><a href="https://imakat.com/2025/08/28/27063/" target="_blank">ID-27063</a>「字幕かんたん作成～」にてご案内しましたが、ウインドウがバラバラに配置され使いにくさがありました。</p>



<figure class="wp-block-image size-large"><a href="https://imakat.com/rd.php?id=aPzCiDJU.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=aPzCiDJU.png" alt=""/></a></figure>



<p class="wp-block-paragraph">今回、大幅に改善しました。各ウインドウを統合して使いやすくしました。誰でも簡単にインストールできるようにしました。</p>



<figure class="wp-block-image size-large is-resized"><a href="https://imakat.com/rd.php?id=Y4ZxRyib.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=Y4ZxRyib.png" alt="" style="aspect-ratio:1.1015516470061926;width:481px;height:auto"/></a></figure>



<h2 class="wp-block-heading"><strong>３　</strong>字幕かんたんエディタの特徴</h2>



<h3 class="wp-block-heading">（１）字幕ファイルはvttを使う</h3>



<p class="wp-block-paragraph">動画アプリの殆どが字幕はsrtファイルを出力する形になっていますが、これは大手プラットフォームのプレーヤーの標準がsrt使用になっているからです。ところが私はWordpressプレーヤーを改良したハンドメイドプレーヤーでの再生を基本に考えていますので、Wordpressの字幕ファイルの標準であるvttを使うことにしています。</p>



<h3 class="wp-block-heading">（２）文字情報はプレーンテキストが基本</h3>



<p class="wp-block-paragraph">私は字幕を動画に焼き付けることは基本的にはしません。その動画の中に焼き付けられた文字の理解のためには、コンピュータの頭脳にかなり大きなエネルギーを使わせることになります。</p>



<p class="wp-block-paragraph">最近、YouTubeなどの動画そのものを生成AIに読み込ませてマルチモーダルに理解させることを売りにしているようですが、こんなことをするとユーザーそれぞれが金太郎飴のように同じことをやり出すわけで、膨大な資源の無駄遣いのように感じます。やはり文字情報はプレーンテキストで記録するのが基本だろうと考えます。</p>



<p class="wp-block-paragraph">だから私は、動画ファイルと字幕ファイルは別々に作ります。再生するときにプレーヤー上で合体させますし、ブログ記事をソース化するときも貼り付けた動画の字幕一覧情報を含めています。</p>



<h3 class="wp-block-heading">（３）字幕一覧txtファイルも同時生成する</h3>



<p class="wp-block-paragraph">字幕vttファイルと同時にセットで字幕一覧txtファイルも出力するようになっています。</p>



<p class="wp-block-paragraph">字幕一覧は、見たい場面に瞬時に移動できる点と、前後の字幕の見逃しや先読みに役に立つ点、それらに加えて、実は、翻訳した時の精度のばらつきをカバーするのに役に立ちます。どういうことかというと、Google翻訳は字幕一覧の中まで綺麗に翻訳出来るのに対して、Apple翻訳は字幕一覧の中の翻訳は使い物にならないほど酷いのにかかわらず画像内の文字列の翻訳は見事に出来るなど、得手不得手がバラバラです。いずれにしても字幕一覧があれば、その凸凹な翻訳レベルを補ってくれます。</p>



<figure class="wp-block-image size-large"><a href="https://imakat.com/rd.php?id=YipZGjwp.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=YipZGjwp.png" alt="" style="aspect-ratio:1.3249879634087627"/></a><figcaption class="wp-element-caption">字幕一覧は、前後の字幕の見逃しや、先読みに役立つ</figcaption></figure>



<figure class="wp-block-image size-large"><a href="https://imakat.com/rd.php?id=byxvsGQk.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=byxvsGQk.png" alt=""/></a><figcaption class="wp-element-caption">Google翻訳は、字幕一覧まで翻訳される。</figcaption></figure>



<figure class="wp-block-image size-large"><a href="https://imakat.com/rd.php?id=GEoHNMsF.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=GEoHNMsF.png" alt=""/></a><figcaption class="wp-element-caption">Apple翻訳は、字幕一覧の中を正しく翻訳できない。</figcaption></figure>



<h2 class="wp-block-heading"><strong>４　</strong>実装</h2>



<h3 class="wp-block-heading">（１）Macの条件</h3>



<p class="wp-block-paragraph">このアプリをインストールできるのは、</p>



<p class="wp-block-paragraph">Apple Silicon (M1 / M2 / M3チップ等) を搭載したMac</p>



<p class="wp-block-paragraph">対応OS：macOS 11 (Big Sur) 以降<br>必要なソフト：なし（完全スタンドアロン）</p>



<p class="wp-block-paragraph">Pythonの環境構築や追加ライブラリのインストールは一切不要です。ダウンロードしてすぐに使用できます。</p>



<p class="wp-block-paragraph">ちなみに私は、M2 Mac mini Sonomaです。</p>



<h3 class="wp-block-heading">（２）インストール方法</h3>



<p class="wp-block-paragraph">必須の操作：初回起動時のセキュリティ許可</p>



<p class="wp-block-paragraph">初回のみ、readme.txt に記載の「右クリック ➔ 開く」によるGatekeeper（開発元未確認）の回避操作が必要です。</p>



<div class="wp-block-cocoon-blocks-toggle-box-1 toggle-wrap toggle-box block-box not-nested-style cocoon-block-toggle"><input id="toggle-checkbox-202609010206090" class="toggle-checkbox" type="checkbox"/><label class="toggle-button" for="toggle-checkbox-202609010206090"><strong><span class="marker">関係スクリプトおよびインストーラ(クリック)</span></strong></label><div class="toggle-content">
<figure class="wp-block-flexible-table-block-table"><table class="has-fixed-layout"><tbody><tr><td>関係するスクリプトは全て公開します。<br>関係スクリプト要約：<br>「字幕かんたんエディタ」は、macOS向けのVTT字幕エディタアプリケーションです。PyWebViewとBottleフレームワークをPythonで利用し、音声波形表示、VTT字幕の新規作成、既存ファイルの編集・保存、そしてWebページ埋め込み用のHTML形式字幕リスト（<code>_listJ.txt</code>）の自動生成といった主要機能を提供します。Finderからの動画ファイル連携起動や、macOSのセキュリティ対策を考慮した初回起動手順が付属しています。<br>アプリは<code>build_dmg.sh</code>スクリプトでDMGファイルとしてビルドされ、readme.txtと共にZIPアーカイブで配布されます。<br>さらに、<code>multimodal_vtt_pipeline_auto.py</code>スクリプトは、Google Gemini 3.6 Flash APIを用いて長尺動画を無音区間で自動分割し、高精度な文字起こしを行う全自動VTT字幕生成パイプラインです。このスクリプトは、日本語の空白整形、タイムスタンプ補正、自動リトライ機能に加え、VTT生成完了後には「字幕かんたんエディタ」を自動起動し、生成されたVTTファイルと元の動画を読み込むことで、字幕の確認・編集作業へのスムーズな連携を実現します。<br>スクリプト↓</td></tr><tr><td><a href="https://imakat.com/script_list/?pubtxt=%E5%AD%97%E5%B9%95%E3%81%8B%E3%82%93%E3%81%9F%E3%82%93%E3%82%A8%E3%83%87%E3%82%A3%E3%82%BFapp%E3%81%AE%E4%BD%9C%E6%88%90_pub.txt" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=zrU95MiY.png" alt="" style="width:120px; height:auto;"></a></td></tr><tr><td>インストーラ↓SubtitleEasyEditor.dmgをクリックしてアプリをドラッグ&amp;ドロップしてください。</td></tr><tr><td><a href="https://imakat.com/rd.php?id=s1U0WvYb.zip" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=sVrfGd4u.png" alt="" style="width:120px; height:auto;"></a></td></tr><tr><td>内容に関するお問い合わせは以下へ↓</td></tr><tr><td><a rel="noopener" target="_blank" href="https://notebooklm.google.com/notebook/aa64cc3d-0160-4433-b934-ecc5ab902287"><img decoding="async" src="https://imakat.com/rd.php?id=vZJJ3Jfw.png" alt="" style="width:200px; height:auto;"></a></td></tr><tr><td>付録：<br>動画を360p音質キープに変換.workflow<br>要約：<br>このワークフローは、入力された動画ファイルを一括して360p解像度に変換するシェルスクリプトです。入力された各ファイルについて、新しいファイル名（元のファイル名に<code>_360p</code>を付加）を生成し、動画処理を行います。<br>動画はアスペクト比を保ちつつ、高さが360ピクセルになるようにスケーリングされます（または幅が360ピクセルになるよう調整され、もう一方が-2で自動設定）。映像コーデックにはH.264（libx264）を使用し、品質はCRF 23、エンコード速度は<code>veryfast</code>プリセット、ピクセルフォーマットは<code>yuv420p</code>が設定されています。<br>音声に関しては、<code>ffprobe</code>で元の音声コーデックとビットレートを判定します。もし元の音声がAACであれば<code>copy</code>モードで劣化なく複製され、それ以外のコーデックであれば、元のビットレート（取得できない場合は256kbps）に近い値でAAC形式に再エンコードされます。最終的に、データサイズの最適化とWeb再生に適した形式で360p動画が出力されます。<br>スクリプト↓</td></tr><tr><td><a href="https://imakat.com/script_list/?pubtxt=動画を360p音質キープに変換_pub.txt" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=zrU95MiY.png" alt="" style="width:120px; height:auto;"></a></td></tr></tbody></table></figure>
</div></div>



<h2 class="wp-block-heading">５　使用方法</h2>



<p class="wp-block-paragraph">説明不要なくらい簡単です。上に貼り付けたビデオでざっくりと説明していますのでご覧ください。</p>



<h2 class="wp-block-heading">６　使用上の注意</h2>



<p class="wp-block-paragraph">私は、字幕vttファイル、字幕一覧txtファイルを新規に作成するときは、自動送信用のmmedia pmediaフォルダではない、別の静的なローカルフォルダに作成するようにしています。その後の、数分で済むような小さな修正は、自動送信用フォルダ内で行います。送信用フォルダは更新の都度、毎分そのファイルを配信所へ送信しますので、あまり処理のラッシュにならないようにしています。</p>



<h2 class="wp-block-heading">７　まとめ</h2>



<p class="wp-block-paragraph">今回の開発で、私にとって「字幕工房」は不要になりました。<br>DIYで作れてしまいます。<br>つくづくアプリ開発で、収入を得るのは、大変な時代になったと感じます。</p>



<p class="wp-block-paragraph">私としては、デジタル創作物もそれなりに広がりと奥行きがついてきました。これまでも意識してきたことではありますが、もっと、このimakatブログを、読者の皆さんの実装に役立つように、充実していきたいと思います。</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">29722</post-id>	</item>
		<item>
		<title>【Mac】ついに完全自動化！右クリック一つで20分以上の動画・音声から高精度マルチモーダルVTT字幕を全自動生成する（無料アカウント対応）</title>
		<link>https://imakat.com/2026/08/21/29651/</link>
		
		<dc:creator><![CDATA[imakat]]></dc:creator>
		<pubDate>Fri, 21 Aug 2026 04:39:05 +0000</pubDate>
				<category><![CDATA[字幕作成]]></category>
		<category><![CDATA[デジタル]]></category>
		<category><![CDATA[ものづくり]]></category>
		<category><![CDATA[vtt]]></category>
		<category><![CDATA[マルチモーダル]]></category>
		<category><![CDATA[ターミナル]]></category>
		<guid isPermaLink="false">https://imakat.com/?p=29651</guid>

					<description><![CDATA[１　はじめに&#160; 今日は、夏休みの工作のようなアプリを一つ。 「Finderから動画・音声ファイルを右クリックするだけで、完全に結合・調整された1本のVTT字幕ファイルが自動でピコンと出来上がる」という、全自動連 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">１　<strong>はじめに</strong>&nbsp;</h2>



<figure class="wp-block-image size-large is-resized"><a href="https://imakat.com/rd.php?id=4ijLOKVB.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=4ijLOKVB.png" alt="" style="width:336px;height:auto"/></a></figure>



<p class="wp-block-paragraph">今日は、夏休みの工作のようなアプリを一つ。</p>



<p class="wp-block-paragraph"><strong>「Finderから動画・音声ファイルを右クリックするだけで、完全に結合・調整された1本のVTT字幕ファイルが自動でピコンと出来上がる」という、全自動連続パイプライン</strong>を開発しました 。これもギターコードアプリと同様、自分専用の介助ロボットづくり、ともいえます。</p>



<p class="wp-block-paragraph"><strong>無料アカウントのままで利用可能</strong>ですのでご安心ください。</p>



<figure class="wp-block-image size-large is-resized"><a href="https://imakat.com/rd.php?id=PuL9LpY9.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=PuL9LpY9.png" alt="" style="aspect-ratio:2.912400455062571;width:355px;height:auto"/></a></figure>



<h2 class="wp-block-heading"><strong>２　なぜGeminiの文字起こしは、VrewやCapCutより優れているのか？</strong></h2>



<p class="wp-block-paragraph">一般的に使われている文字起こしツールは、純粋に「音声だけ」を聴いて文字に起こす仕組みになっているようです。そのため、同音異義語や画面に映っている言葉と乖離した誤変換が起きやすく、後からの手直しが避けられません。</p>



<p class="wp-block-paragraph">しかし、Gemini 1.5 Pro（またはFlash、今回制作したPythonの中ではgemini-3.6-flashを使っています。）などGeminiモデルの最大の特徴は～「目（映像解析）」と「耳（音声解析）」の両方を同時に使うマルチモーダル処理～が可能なことです。 動画内のスライド、背後のホワイトボードの文字、話している文脈をGeminiが総合的に理解して文字起こしを行うため、専門用語や固有名詞の変換精度が非常に高く、手直しの手間がとても少なくなります。</p>



<figure class="wp-block-image size-large is-resized"><a href="https://imakat.com/rd.php?id=ZhgpN5e8.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=ZhgpN5e8.png" alt="" style="aspect-ratio:1.832694992304113;width:332px;height:auto"/></a></figure>



<h2 class="wp-block-heading"><strong>３　拡がる字幕情報の活用</strong></h2>



<p class="wp-block-paragraph"><br>さらに今回は、～「映像と音声から複数人の会話（対談やインタビュー）だと自動判断した場合のみ、字幕の先頭に [男性]： [女性]： などの話者ラベルを自動で付与する」～という命令（話者判別ロジック）をプロンプトに組み込みました。これにより、例えば、Notebookで2人の音声解説を作る→それをこのアプリでVTT形式で文字起こしする→それをそのままPodcastや音声ブログにする、あるいはさらに、そこから動画を生成することもできます。</p>



<h2 class="wp-block-heading"><strong>４　サンプルとして「聴くブログ」の字幕の出来栄えをご確認ください。</strong></h2>



<p class="wp-block-paragraph">今回のアプリで作成したサンプルです。以下の「聴くブログ」をクリックすると表示されます。内容は結構お硬いですが高校生に理解できる程度の会話です。音声と連動して表示される字幕を確認ください。サイドバーにも掲載しています。</p>



<div style="text-align: left; margin-bottom: 15px;">

<!-- 
  ★配信したいdrid（例：drid=m51）をhrefの中に記述しておくだけで、
  バナー画像の下のテキスト（題名）はスプレッドシートから自動取得され完璧に連動します。
-->
<a href="https://imakat.com/mu1emd_autoplay/?drid=m52&#038;frag=1" id="podcastBannerLink" target="_blank" style="display: block; text-align: center;">
  <img decoding="async" src="https://imakat.com/rd.php?id=uKexYnKu.png
" alt="Podcast imakat図書館" style="max-width: 100%; height: auto; border-radius: 6px;">
</a>

<!-- ★ここのテキストは、ページ読み込み時に最新の「題名」へ自動で書き換わります -->
<p id="podcastBannerTitle" style="font-size: 13px; font-weight: bold; color: #333; margin: 6px 0 0 0; line-height: 1.4; text-align: left;">
  読み込み中&#8230;
</p>

<script>
(function(){
  var link = document.getElementById('podcastBannerLink');
  var titleEl = document.getElementById('podcastBannerTitle');
  if(!link || !titleEl) return;

  // URLからdridパラメータ（例: m51）を抽出
  var dridMatch = link.href.match(/[?&]drid=([^&]+)/);
  var drid = dridMatch ? dridMatch[1] : '';

  if (drid) {
    // WordPress側プロキシ経由で最新のvideoct.jsonを取得して連動
    fetch('https://imakat.com/gd_proxy/?f=videoct.json')
      .then(function(res) { return res.json(); })
      .then(function(data) {
        var v = data.find(function(item) { return String(item.videoid) === String(drid); });
        if (v && v.title) {
          titleEl.textContent = v.title; // ★スプレッドシートの最新の「題名」を自動反映
        } else {
          titleEl.textContent = 'imakat図書館Podcast'; // 万が一見つからない場合のフォールバック
        }
      })
      .catch(function() {
        titleEl.textContent = 'imakat図書館Podcast'; // エラー時の安全フォールバック
      });
  }

  // スマホとPCでの最適な挙動制御
  var isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
  link.addEventListener('click', function(e){
    if(!isMobile) {
      e.preventDefault();
      var w = 430, h = 530;
      var left = (screen.width - w) / 2;
      var top = (screen.height - h) / 2;
      var features = 'width=' + w + ',height=' + h + ',left=' + left + ',top=' + top + ',personalbar=0,toolbar=0,scrollbars=1,resizable=1';
      var popup = window.open(this.href, 'podcast_popup', features);
      if(popup) {
        popup.focus();
      } else {
        window.open(this.href, '_blank');
      }
    }
  });
})();
</script>

</div>



<p class="wp-block-paragraph">この字幕情報の作り方ですが、Notebookで20分程度の音声解説を作りそれを今回のアプリで時刻付きの文字起こしVTTファイルを作成→次にそれをインタラクティブ・トランスクリプト(Interactive Transcript/対話型文字起こし型txtファイル)形式のtxtファイルに変換して同期させています。<br>インタラクティブ・トランスクリプト形式のtxtファイルの記述例：</p>



<pre class="wp-block-code plaintext"><code>&lt;details translate="yes"&gt;&lt;summary translate="yes"&gt;字幕一覧(クリック)&lt;/summary&gt; &lt;p translate="yes"&gt;
(&lt;a href="#" class="imk-cue" data-seek="0:16" translate="no"&gt;00:00:16&lt;/a&gt;) ディープダイブへようこそ。&lt;br&gt;
(&lt;a href="#" class="imk-cue" data-seek="0:17" translate="no"&gt;00:00:17&lt;/a&gt;) &#091;男性]：今日のテーマなんですが、ずばり「妥協なきデジタル主権への挑戦」ということで。&lt;br&gt;
(&lt;a href="#" class="imk-cue" data-seek="0:25" translate="no"&gt;00:00:25&lt;/a&gt;) &#091;女性]：デジタル主権、響きからして壮大ですよね。巨大IT企業に依存しないで、自分自身でデータを完全にコントロールするインフラをどうやって獲得するのか。&lt;br&gt;

&lt;/p&gt; &lt;/details&gt;
&lt;style&gt;
details { font: 16px "Open Sans", Calibri, sans-serif; width: 100%; }
details &gt; summary { padding: 2px 6px; width: 100%; background-color: #ddd; border: none; box-shadow: 3px 3px 4px black; cursor: pointer; list-style: none; }
details &gt; p { font: 14px "Open Sans", Calibri, sans-serif; height:150px; overflow: scroll; background-color: #EDF7FF; padding: 2px 6px; margin: 0; box-shadow: 3px 3px 4px black; }
&lt;/style&gt;</code></pre>



<p class="wp-block-paragraph">なお、このテストでは、字幕作成の対象ファイルに、動画ではなくmp3音声ファイルを選んでいますが問題なく処理されています。</p>



<p class="wp-block-paragraph">全自動作成後のVTTに対する修正箇所ですが、ロールバック機構→フォールバック機構、DR52.php→dr52.php、MyBox→mybox、 MyBox_1→mybox_1、の修正だけです。句読点もそのまま、時刻もそのままです。このサンプルは動画内に表示された文字を理解させるテストにはなっていませんが、それにしてもかなり高い精度です。</p>



<h2 class="wp-block-heading"><strong>５　無料アカウントでも20分以上の動画がエラーなく処理できる秘密</strong></h2>



<p class="wp-block-paragraph">Google AI Studio（API）の無料枠を利用する場合でも、長尺の動画は容量や処理限界の壁にぶつかります。 今回のパイプラインスクリプトは、以下の工程をすべて「Python」とMac標準ツールがバックグラウンドで全自動実行します。</p>



<p class="wp-block-paragraph"><strong>（１）自動・無音分割（FFmpeg）</strong>: 動画を読み込み、1.0秒以上の無音区間を自動で探し出し、キリの良いポイント（デフォルト4分30秒）で動画を再エンコードなし（一瞬）で自動分割。<br></p>



<p class="wp-block-paragraph"><strong>（２）クリーンな並列API送信</strong>: 分割した各動画を、それぞれ毎回クリーンな別のAIセッションにアップロードして文字起こしを依頼（AIの脳内メモリの混線を100%防止）。</p>



<p class="wp-block-paragraph"><strong>（３）自動クリーンアップ</strong>: 処理が終わった動画ファイルは、APIサーバー上から直ちに自動削除（サーバー容量を圧迫しません）。<br></p>



<p class="wp-block-paragraph"><strong>（４）自動タイムシフト＆マージ</strong>: 0秒リセットで返ってきた各字幕のタイムスタンプに、分割時のオフセット秒（ズレ）を自動で足し算して復元。<br></p>



<p class="wp-block-paragraph"><strong>（５）通し連番の再編成</strong>: 結合された巨大なVTTデータを走査し、1から始まる綺麗な通し連番に一瞬で自動振り直し。<br></p>



<p class="wp-block-paragraph"><strong>（６）デスクトップ通知</strong>: すべてが完了すると、Macの画面右上に「字幕作成が完了しました！」と通知が表示されます。<br></p>



<p class="wp-block-paragraph">人間がやることは～「右クリックして待つだけ」～です。</p>



<h2 class="wp-block-heading">６　実装手順</h2>



<p class="wp-block-paragraph">それでは、実装手順は以下です。</p>



<div class="wp-block-cocoon-blocks-toggle-box-1 toggle-wrap toggle-box block-box not-nested-style cocoon-block-toggle"><input id="toggle-checkbox-202609020607300" class="toggle-checkbox" type="checkbox"/><label class="toggle-button" for="toggle-checkbox-202609020607300"><strong><span class="marker">実装手順(クリック)</span></strong></label><div class="toggle-content">
<figure class="wp-block-flexible-table-block-table"><table class="has-fixed-layout"><tbody><tr><td>&#x1f6e0;&#xfe0f; <strong>実装手順</strong><br><strong>（１）Geminiの「APIキー」を無料取得する</strong><br>APIを利用するための「鍵」を入手します。<br><strong>Google AI Studioの公式サイト</strong>（ https://aistudio.google.com/ ）にアクセスします。<br>画面左上の <strong>「Get API key」</strong> ボタンを押し、プロジェクト用のキー（無料）を作成してコピーします。<br><br><strong>（２）MacにPython 3とFFmpegをインストールする</strong><br>お使いのMacのターミナル（黒い画面）を開き、以下のコマンドが通る状態にしてください。 （※すでにHomebrewが導入されている環境を推奨します）<br>brew install ffmpeg<br>pip install google-genai<br><br><strong>（３） スクリプトの保存<br>１）Python</strong><br>以下の青ボタンからPythonコードをダウンロードまたはコピーし、プログラム内の `API_KEY` の部分にご自身のAPIキーを貼り付けた上で、Macの適当なフォルダ（例: `/Users/（あなたのユーザー名）/python_scripts/` ）に **`multimodal_vtt_pipeline.py`** という名前で保存します。<br><br><strong>２）AppleScript</strong><br>同じく以下の青ボタンからAppleScriptコードをダウンロードまたはコピーして、<br>①Mac標準の <strong>「Automator」</strong> アプリを起動し、新規作成から <strong>「クイックアクション」</strong> を選択します。<br>②画面最上部の設定を以下のように設定します。<br>「ワークフローの受け取る項目:」 ➔ 「ファイルまたはフォルダ」<br>「検索対象:」 ➔ <strong>「Finder.app」</strong><br><br>③左側のアクション一覧から <strong>「AppleScriptを実行」</strong> を探し、右側のワークフローエリアにドラッグ＆ドロップします。<br>④AppleScriptのコード入力欄に、ダウンロードしたコードを貼り付けます。 （※保存したPythonスクリプトのパスやPython実行環境のパスは、ご自身の環境に合わせて適切に書き換えてください）<br>⑤名前(全自動VTT字幕作成)をつけて保存します。</td></tr><tr><td><a href="https://imakat.com/script_list/?pubtxt=%E5%85%A8%E8%87%AA%E5%8B%95%E3%81%A7%E5%8B%95%E7%94%BB%E3%81%8B%E3%82%89vtt%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E7%94%9F%E6%88%90%E3%81%99%E3%82%8B_pub.txt" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=zrU95MiY.png" alt="" style="width:120px; height:auto;"></a></td></tr><tr><td>内容に関するお問い合わせは以下へ↓</td></tr><tr><td><a rel="noopener" target="_blank" href="https://notebooklm.google.com/notebook/aa64cc3d-0160-4433-b934-ecc5ab902287"><img decoding="async" src="https://imakat.com/rd.php?id=vZJJ3Jfw.png" alt="" style="width:200px; height:auto;"></a></td></tr></tbody></table></figure>
</div></div>



<h2 class="wp-block-heading">７　<strong>運用上の注意</strong></h2>



<p class="wp-block-paragraph"><strong>（１）ターミナルが起動するようにしてあります。</strong></p>



<p class="wp-block-paragraph">私は有料AI Proですが、それでも使用制限は結構な頻度で生じます。エラーやトラブルも時々生じます。そうした異常が生じた時、ターミナルに表示された文字列をコピーして、GeminiやGemなどの生成AIに貼り付けて、その末尾を改行してその下に「エラーが生じました。対策を教えてください。」と書いて、送信することになります。<br>その時の別な方法として、logを調べてそれをターミナルにコピペする方法もありますが、分かりにくく一手間余分に掛かりますので、ターミナルを常に表示するようにしておくのが、私にとっては安心に感じています。</p>



<figure class="wp-block-image size-large is-resized"><a href="https://imakat.com/rd.php?id=oLB8YtHo.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=oLB8YtHo.png" alt="" style="aspect-ratio:0.7934048224136863;width:427px;height:auto"/></a><figcaption class="wp-element-caption">ターミナルの表示例</figcaption></figure>



<p class="wp-block-paragraph"><strong>（２）GeminiやGemなど生成AIはすぐに使えるようにしておく</strong></p>



<p class="wp-block-paragraph">ブラウザのブックマークや専用アプリなどを用意して、Gemini/Gem/生成AI、がすぐに使えるようにしておきましょう。エラーやトラブルの時、すぐにターミナルのコピペができるようにしておきましょう。</p>



<p class="wp-block-paragraph">以上</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">29651</post-id>	</item>
		<item>
		<title>DropboxからGoogleドライブへ乗り換えることにしました</title>
		<link>https://imakat.com/2026/08/04/29546/</link>
		
		<dc:creator><![CDATA[imakat]]></dc:creator>
		<pubDate>Tue, 04 Aug 2026 13:06:42 +0000</pubDate>
				<category><![CDATA[デジタル]]></category>
		<category><![CDATA[ものづくり]]></category>
		<category><![CDATA[昔の名前で出ています]]></category>
		<category><![CDATA[Dropbox]]></category>
		<category><![CDATA[Googleドライブ]]></category>
		<category><![CDATA[フォルダパス]]></category>
		<guid isPermaLink="false">https://imakat.com/?p=29546</guid>

					<description><![CDATA[はじめに&#160; そろそろ届く頃かなとは思っていたところですが。 Dropboxの年額更新案内が届きました。もともと課題意識があったので準備を進めてきたことではありますが、クラウドストレージの運用体制を見直し、Goo [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading"><strong>はじめに</strong>&nbsp;</h2>



<p class="wp-block-paragraph">そろそろ届く頃かなとは思っていたところですが。</p>



<p class="wp-block-paragraph">Dropboxの年額更新案内が届きました。もともと課題意識があったので準備を進めてきたことではありますが、クラウドストレージの運用体制を見直し、Googleドライブ（Google AI Proプラン）への全面移行を進めることにしました。</p>


<div class="sc-dynamic-embed">
  <style>
  /* リンクの見た目を整える */
  .sc-dynamic-embed .sc-link-container { 
      display: flex; 
      margin-bottom: 10px; 
      flex-wrap: wrap; 
      align-items: center; 
  }
  .sc-dynamic-embed .sc-link { margin-bottom: 0; }
  .sc-dynamic-embed .sc-link a {
    font-size: 15px; 
    font-weight: normal;
    text-decoration: underline;
    color: #0073aa;
  }
  .sc-dynamic-embed .sc-link a:hover { text-decoration: none; color: #000; }
  
  @media (max-width: 500px) {
    .sc-dynamic-embed .sc-link a { font-size: 13px; }
  }

  .sc-dynamic-embed .imk-line {
      display: inline-block;
      width: 100%;
      border-radius: 2px;
      transition: background-color 0.1s;
  }

  .sc-dynamic-embed #subtitleOverlay,
  .sc-dynamic-embed #scSubtitleOverlay,
  .sc-dynamic-embed .overlay-cue,
  .sc-dynamic-embed .band {
      display: none !important;
      opacity: 0 !important;
      visibility: hidden !important;
      pointer-events: none !important;
  }
  </style>

  <div class="sc-link-container">
    <p class="sc-link">
      <a href="https://imakat.com/ds62/?drid=104" target="_blank"
         onclick="return scStopAndGo(event, this);">
        👉低画質・枠外字幕はこちら
      </a>
    </p>
  </div>

  <style>
            :root{ --dr5emd-max: 1920px; }
            #subtitleOverlay, #scSubtitleOverlay, .overlay-cue, .band { display: none !important; opacity: 0 !important; }
            .imk-line { display: inline-block; width: 100%; border-radius: 2px; transition: background-color 0.1s; }
            .dr5emd-sublist details > p{ height:200px; overflow:auto; background-color:#EDF7FF; padding:2px 6px; margin:0; box-shadow:3px 3px 4px black; position: relative; }
            .dr5emd-sublist details > summary{ padding:2px 6px; width:100%; background-color:#ddd; border:none; box-shadow:3px 3px 4px black; cursor:pointer; list-style:none; }
            
                .video-wrap{position:relative;width:100%;margin:0 auto}
                #myVideo{width:100%;height:auto;min-height:200px;display:block;background:#000;}</style><div class="dr5emd-container"><div class="video-wrap"><video id="myVideo" controls poster="https://imakat.com/rd.php?id=wkh7padF.png" playsinline preload="none" style="width:100%;height:auto;">  <source src="https://imakat.com/rd.php?id=Cm7I7Q9n.mp4" type="video/mp4">  <track src="https://imakat.com/rd.php?id=SaRGyfFo.vtt" label="日本語" srclang="ja" kind="subtitles"></video></div><div class="dr5emd-sublist"><details translate="yes"><summary translate="yes">字幕一覧(クリック)</summary> <p translate="yes">
(<a href="#" class="imk-cue" data-seek="0:06" translate="no">00:00:06</a>) ちょっと8月に入ってですね、困ったことが起きまして、<br>
(<a href="#" class="imk-cue" data-seek="0:12" translate="no">00:00:12</a>) メールで案内が来たんですけども、Dropboxのですね、年度更新が来ました。<br>
(<a href="#" class="imk-cue" data-seek="0:22" translate="no">00:00:22</a>) それがですね、今私2TBのPlusというサービスを使っているんですけども、<br>
(<a href="#" class="imk-cue" data-seek="0:34" translate="no">00:00:34</a>) それがUSドルでですね、131.87USドルということになってます。これ年間ですね。<br>
(<a href="#" class="imk-cue" data-seek="0:44" translate="no">00:00:44</a>) 131.87USドル。<br>
(<a href="#" class="imk-cue" data-seek="0:49" translate="no">00:00:49</a>) これをですね、仮に160円/USドルで計算すると、なんと21,000円ですよね。<br>
(<a href="#" class="imk-cue" data-seek="0:59" translate="no">00:00:59</a>) これ最初に私がDropboxを契約した頃っていうのは、どうだろう、月1,000円ぐらいで済んでたと思うんですよね。<br>
(<a href="#" class="imk-cue" data-seek="1:11" translate="no">00:01:11</a>) 年間21,000円ってことは、12で割ると月1,750円ってことになるんですよ。<br>
(<a href="#" class="imk-cue" data-seek="1:23" translate="no">00:01:23</a>) 本当に下手するともう2,000円っていうぐらいになってしまうので、なんとかですね、これは脱Dropboxを考えていきたいなと思っていたわけですね。<br>
(<a href="#" class="imk-cue" data-seek="1:39" translate="no">00:01:39</a>) 実はそういう思いでずっといまして、ちょっとやっぱり2TBをですね、月1,500円以上払って使い続けるのももったいないなと。<br>
(<a href="#" class="imk-cue" data-seek="1:54" translate="no">00:01:54</a>) そこへ例のGoogle AI Proがですね、月2,900円で5TBのストレージを含むというサービスを始めましたよね。<br>
(<a href="#" class="imk-cue" data-seek="2:09" translate="no">00:02:09</a>) まあちょっとどう考えてもこれを使った方がいいなと思い出しまして、今回ですね、こう判断をしました。<br>
(<a href="#" class="imk-cue" data-seek="2:22" translate="no">00:02:22</a>) それはですね、M1 Mac miniですけども、一つNO3_SSDっていうのをくっつけてるんですけども、<br>
(<a href="#" class="imk-cue" data-seek="2:35" translate="no">00:02:35</a>) これの下に今Dropboxのローカルフォルダーが入ってます。<br>
(<a href="#" class="imk-cue" data-seek="2:46" translate="no">00:02:46</a>) でそれをですね、Dropboxの契約を停止して、その代わり<br>
(<a href="#" class="imk-cue" data-seek="2:57" translate="no">00:02:57</a>) Gドライブの方にですね、mybox_1というフォルダーを作って、そこへですね、全部移すと。<br>
(<a href="#" class="imk-cue" data-seek="3:10" translate="no">00:03:10</a>) 全部移すというのはクラウド側を移すということです。<br>
(<a href="#" class="imk-cue" data-seek="3:16" translate="no">00:03:16</a>) ただしですね、私の場合このDropboxという名前を使って、非常にたくさんのですね、アプリを作ってきてるんですね。<br>
(<a href="#" class="imk-cue" data-seek="3:28" translate="no">00:03:28</a>) フォルダーパス、ファイルパスにかなりたくさん使ってきてますので、これを全部ですね、mybox何がしに替えるっていうのは大変な作業なんで、<br>
(<a href="#" class="imk-cue" data-seek="3:42" translate="no">00:03:42</a>) まあこれは考え方だけしっかりしておけばいいので、<br>
(<a href="#" class="imk-cue" data-seek="3:47" translate="no">00:03:47</a>) ローカルフォルダー名としてはこのDropboxを残す。で、Gドライブ、パソコン、マイMacmini、mybox_1へミラーリング同期すると。という形ですね、しようと思います。<br>
(<a href="#" class="imk-cue" data-seek="4:12" translate="no">00:04:12</a>) まあそんな形でですね、いよいよDropboxの脱出を図り、データを移したあとでちょっと様子を見てですね、勇気をもってDropboxを解約したいと考えています。<br>
(<a href="#" class="imk-cue" data-seek="4:33" translate="no">00:04:33</a>) あと補足しますけども、<br>
(<a href="#" class="imk-cue" data-seek="4:38" translate="no">00:04:38</a>) DropboxをやめてGoogleドライブに切り替えた時にですね、<br>
(<a href="#" class="imk-cue" data-seek="4:44" translate="no">00:04:44</a>) 今まで作ってきたアプリケーションに、<br>
(<a href="#" class="imk-cue" data-seek="4:50" translate="no">00:04:50</a>) 何か様々な悪影響が出てくるんじゃないかなという懸念があったわけですね。<br>
(<a href="#" class="imk-cue" data-seek="4:57" translate="no">00:04:57</a>) 特に私の場合、マイライブラリというですね、<br>
(<a href="#" class="imk-cue" data-seek="5:02" translate="no">00:05:02</a>) メディアアセットをコントロールするライブラリ、まあ図書館を作っていたわけですね。<br>
(<a href="#" class="imk-cue" data-seek="5:10" translate="no">00:05:10</a>) で、この図書館からですね、各<br>
(<a href="#" class="imk-cue" data-seek="5:17" translate="no">00:05:17</a>) 配信サーバーにですね、メディアアセットを送ると<br>
(<a href="#" class="imk-cue" data-seek="5:24" translate="no">00:05:24</a>) という働きをですね、システム化してきたわけですね。<br>
(<a href="#" class="imk-cue" data-seek="5:31" translate="no">00:05:31</a>) その悪影響がないかなってことなんですね。<br>
(<a href="#" class="imk-cue" data-seek="5:35" translate="no">00:05:35</a>) と言いますのは、例えば、<br>
(<a href="#" class="imk-cue" data-seek="5:39" translate="no">00:05:39</a>) 必ずですね、この<br>
(<a href="#" class="imk-cue" data-seek="5:45" translate="no">00:05:45</a>) 登録してあるものの中にDropboxリンクっていうのを必ず登録してきました。<br>
(<a href="#" class="imk-cue" data-seek="5:54" translate="no">00:05:54</a>) WordPressのエンコードURLとかですね、pCloudのURLとか、そういうものをリンクしてきたわけですね。<br>
(<a href="#" class="imk-cue" data-seek="6:04" translate="no">00:06:04</a>) で、このDropboxリンクっていうのがですね、これから契約解除すると、<br>
(<a href="#" class="imk-cue" data-seek="6:13" translate="no">00:06:13</a>) そこがですね、エラーにならないかっていうことが非常に気になっていたわけです。<br>
(<a href="#" class="imk-cue" data-seek="6:21" translate="no">00:06:21</a>) で、そこでですね、今どのようにしたかというと、これ一番最初、一行目テスト登録っていうのがありますけども、<br>
(<a href="#" class="imk-cue" data-seek="6:29" translate="no">00:06:29</a>) その中にはですね、すでにDropboxという選択肢は出てきてないです。<br>
(<a href="#" class="imk-cue" data-seek="6:39" translate="no">00:06:39</a>) 次のこのマイコードアプリ、こちらを見ると、ここにはDropboxリンクって出てますけども、<br>
(<a href="#" class="imk-cue" data-seek="6:46" translate="no">00:06:46</a>) その前の、その次の最新のテスト登録pngを見た場合、<br>
(<a href="#" class="imk-cue" data-seek="6:53" translate="no">00:06:53</a>) もうここにはDropboxリンクというものが登場してません。<br>
(<a href="#" class="imk-cue" data-seek="6:58" translate="no">00:06:58</a>) で、ファイルを見てみますとですね、どうなってるかということですけども、<br>
(<a href="#" class="imk-cue" data-seek="7:05" translate="no">00:07:05</a>) これがファイルですね。F1マイライブラリというファイルですけども、<br>
(<a href="#" class="imk-cue" data-seek="7:15" translate="no">00:07:15</a>) スプレッドシートです。で、それのA列のところにDropboxリンクURLがあります。<br>
(<a href="#" class="imk-cue" data-seek="7:17" translate="no">00:07:17</a>) で、この今ですね、<br>
(<a href="#" class="imk-cue" data-seek="7:24" translate="no">00:07:24</a>) 一番最近登録してあるこのテスト登録pngというファイルですけども、<br>
(<a href="#" class="imk-cue" data-seek="7:34" translate="no">00:07:34</a>) それはですね、これご覧のようにDropboxリンクのところは空欄になってます。<br>
(<a href="#" class="imk-cue" data-seek="7:41" translate="no">00:07:41</a>) 空欄にしておきながら、wpidexといういわゆるユニークIDはちゃんと設定してですね、<br>
(<a href="#" class="imk-cue" data-seek="7:51" translate="no">00:07:51</a>) 配信する時にいつものようにユニークなURLを使うことができるという仕組みにですね、改造をしました。<br>
(<a href="#" class="imk-cue" data-seek="7:59" translate="no">00:07:59</a>) ま、これによってDropboxリンクのところは空欄になるんですけれども、<br>
(<a href="#" class="imk-cue" data-seek="8:07" translate="no">00:08:07</a>) Dropboxを使って配信をしなければいいだけですね。<br>
(<a href="#" class="imk-cue" data-seek="8:12" translate="no">00:08:12</a>) そういう扱い、運用をすれば問題がないということを確認しました。<br>
(<a href="#" class="imk-cue" data-seek="8:19" translate="no">00:08:19</a>) つまり、この<br>
(<a href="#" class="imk-cue" data-seek="8:24" translate="no">00:08:24</a>) WP再生URLをクリックしてですね、このようにデータが正しく表示されます。<br>
(<a href="#" class="imk-cue" data-seek="8:33" translate="no">00:08:33</a>) それでいいわけですね。<br>
(<a href="#" class="imk-cue" data-seek="8:35" translate="no">00:08:35</a>) そういう形でですね、プログラムの手直しをしたということをやってきました。<br>
(<a href="#" class="imk-cue" data-seek="8:43" translate="no">00:08:43</a>) これをもってして安心して解約ができると考えています。以上です。<br>
</p> </details>
<style>
/* 汎用ベーススタイル（マイライブラリ等の単独表示・ブログ埋め込み用） */
details { font: 16px "Open Sans", Calibri, sans-serif; width: 100%; }
details > summary { padding: 2px 6px; width: 100%; background-color: #ddd; border: none; box-shadow: 3px 3px 4px black; cursor: pointer; list-style: none; }
details > p { font: 14px "Open Sans", Calibri, sans-serif; height:150px; overflow: scroll; background-color: #EDF7FF; padding: 2px 6px; margin: 0; box-shadow: 3px 3px 4px black; }

/* 動画パッケージ（mu1emd, dr5, dr6等）内での上書き専用スタイル */
.mu1emd-sublist details > p, .dr5-sublist details > p, .dr6-sublist details > p {
    display: block;
    min-height: 180px;
    max-height: 240px;
    overflow-y: auto;
    background-color: #EDF7FF;
    padding: 6px 8px;
    margin: 0;
    box-shadow: 3px 3px 4px black;
    border-radius: 0 0 5px 5px;
}
</style>
</div></div>            <script>
            document.addEventListener("DOMContentLoaded", function(){
                var video = document.getElementById("myVideo");
                if(!video) return;

                            });
            </script>
            

  <script>
  (function(){
    var me = document.currentScript;
    var wrapper = me ? me.closest('.sc-dynamic-embed') : null;

    function protectVideo() {
      var target = wrapper ? wrapper : document;
      var mediaEls = target.querySelectorAll('video, audio');
      mediaEls.forEach(function(v){
        if(v.dataset.protected === 'true') return;
        v.dataset.protected = 'true';
        v.setAttribute('controlsList', 'nodownload');
        v.oncontextmenu = function() { return false; };
        v.addEventListener('contextmenu', function(e){ e.preventDefault(); return false; }, false);
      });
    }

    function initSubtitles() {
      var target = wrapper ? wrapper : document;
      var video = target.querySelector('video, audio');
      var listContainer = target.querySelector('details > p');
      
      if (!video) return false; 
      
      if (video.dataset.subInit === 'true') return true; 
      video.dataset.subInit = 'true';

      var oldOverlay = target.querySelector('#subtitleOverlay') || target.querySelector('#scSubtitleOverlay');
      if (oldOverlay) {
          oldOverlay.style.setProperty('display', 'none', 'important');
          oldOverlay.innerHTML = ''; 
      }

      function isSpecialMode() {
        var isFs = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement);
        var isPiP = !!(document.pictureInPictureElement && document.pictureInPictureElement === video) || (video.webkitPresentationMode === "picture-in-picture");
        var isIOSFs = !!video.webkitDisplayingFullscreen; 
        return isFs || isPiP || isIOSFs;
      }

      // 初期状態のセット（リストがなければ最初から showing に）
      try {
        if(video.textTracks && video.textTracks.length > 0){
          for(var i=0; i<video.textTracks.length; i++){
             if(video.textTracks[i].kind === 'subtitles' || video.textTracks[i].kind === 'captions'){
                 video.textTracks[i].mode = (!listContainer) ? "showing" : "hidden";
             }
          }
        }
      } catch(e){}

      // 字幕一覧が存在する場合のみ、リストのフォーマット処理を行う
      if (listContainer) {
          var detailsEl = target.querySelector("details");
          if (detailsEl) {
              detailsEl.open = true; 
              var summaryEl = detailsEl.querySelector("summary");
              if (summaryEl) summaryEl.textContent = "字幕(シーン)はここをクリック";
          }

          if (!listContainer.dataset.formatted) {
              var html = listContainer.innerHTML;
              var lines = html.split(/<br\s*\/?>/i);
              var newHtml = "";
              for(var j=0; j<lines.length; j++) {
                  if(lines[j].trim() === "") continue;
                  newHtml += "<span class='imk-line'>" + lines[j] + "</span><br>";
              }
              listContainer.innerHTML = newHtml;
              listContainer.dataset.formatted = "true";
          }

          var rootSublist = target.querySelector(".dr5-sublist") || listContainer.parentElement;
          if (rootSublist) {
            rootSublist.addEventListener("click", function(e){
              var a = e.target.closest && e.target.closest("a.imk-cue[data-seek]");
              if(!a) return;
              e.preventDefault();
              var ts = a.getAttribute("data-seek");
              if(!ts) return;
              var p = ts.trim().split(":").map(function(x){return parseInt(x,10)||0;});
              var sec = (p.length===2) ? p[0]*60+p[1] : (p.length===3 ? p[0]*3600+p[1]*60+p[2] : null);
              if(sec==null) return;
              try{ video.currentTime = sec; if(video.paused) video.play(); }catch(_){}
            });
          }
      }

      // ★ dr52.php と全く同じ「常時監視」のロジック
      // これにより、Safariが読み込み遅延を起こしても確実にONになります
      video.addEventListener("timeupdate", function(){
        var hasList = (listContainer !== null);
        var desiredMode = (isSpecialMode() || !hasList) ? "showing" : "hidden";
        
        try {
            if(video.textTracks && video.textTracks.length > 0){
                for(var i=0; i<video.textTracks.length; i++){
                    if((video.textTracks[i].kind === 'subtitles' || video.textTracks[i].kind === 'captions') && video.textTracks[i].mode !== desiredMode) {
                        video.textTracks[i].mode = desiredMode;
                    }
                }
            }
        } catch(e){}

        // リストがない場合はこれ以降のハイライト処理をスキップ
        if (!hasList) return;

        var cues = listContainer.querySelectorAll("a.imk-cue");
        if(cues.length === 0) return;
        var cur = video.currentTime;
        var activeA = null;

        for(var i=0; i<cues.length; i++){
            var ts = cues[i].getAttribute("data-seek");
            if(!ts) continue;
            var p = ts.trim().split(":").map(function(x){return parseInt(x,10)||0;});
            var t = (p.length===2) ? p[0]*60+p[1] : (p.length===3 ? p[0]*3600+p[1]*60+p[2] : null);
            if(t !== null && cur >= t - 0.5){ activeA = cues[i]; } 
            else if(t > cur){ break; }
        }

        if(activeA){
            var activeLine = activeA.closest(".imk-line");
            if(!activeLine) activeLine = activeA;

            if(activeLine.classList.contains("active-hl")) return;

            var allLines = listContainer.querySelectorAll(".imk-line");
            for(var k=0; k<allLines.length; k++) {
                allLines[k].classList.remove("active-hl");
                allLines[k].removeAttribute("style"); 
            }
            var allLinks = listContainer.querySelectorAll("a");
            for(var m=0; m<allLinks.length; m++) {
                allLinks[m].classList.remove("active-hl");
                allLinks[m].removeAttribute("style"); 
            }

            activeLine.classList.add("active-hl");
            activeLine.style.setProperty("background-color", "#ffff00", "important");
            activeLine.style.setProperty("color", "red", "important");
            activeLine.style.setProperty("font-weight", "normal", "important");
            
            var newLinks = activeLine.querySelectorAll("a");
            for(var n=0; n<newLinks.length; n++) {
                newLinks[n].style.setProperty("color", "red", "important");
                newLinks[n].style.setProperty("text-decoration", "none", "important");
            }

            if(listContainer.offsetParent !== null){
                var containerRect = listContainer.getBoundingClientRect();
                var activeRect = activeLine.getBoundingClientRect();
                var targetScroll = listContainer.scrollTop + (activeRect.top - containerRect.top) - (listContainer.clientHeight / 2) + (activeLine.clientHeight / 2);
                listContainer.scrollTo({ top: targetScroll, behavior: "smooth" });
            }
        }
      });

      return true;
    }

    var checks = 0;
    var checkTimer = setInterval(function(){
      protectVideo();
      var success = initSubtitles();
      checks++;
      if (success || checks > 20) { 
        clearInterval(checkTimer);
      }
    }, 500); 

    if (!window.scStopAndGo) {
      window.scStopAndGo = function(event, link){
        try{
          var mediaEls = document.querySelectorAll('video, audio');
          mediaEls.forEach(function(m){
            try{
              if (!m.paused) m.pause();
              if (document.pictureInPictureElement === m && document.exitPictureInPicture) {
                document.exitPictureInPicture().catch(function(){});
              }
            }catch(e){}
          });
        }finally{
          event.preventDefault();
          setTimeout(function(){
            if (link.target === '_blank') {
              window.open(link.href, '_blank');
            } else {
              window.location.href = link.href;
            }
          }, 50);
        }
        return false;
      };
    }
  })();
  </script>
</div>



<figure class="wp-block-image size-full is-resized"><img data-recalc-dims="1" fetchpriority="high" decoding="async" width="472" height="209" src="https://i0.wp.com/imakat.com/wp-content/uploads/2026/08/4fb0a64bd89800a8722405fa6ed6f421-1.png?resize=472%2C209&#038;ssl=1" alt="" class="wp-image-29566" style="width:207px;height:auto"/></figure>



<p class="wp-block-paragraph">長年使い込んできたDropboxからの脱却にあたり、課題となった「ローカルパスの互換性」と「自作アプリ（AppSheet / WordPress連携）への影響」をどのようにクリアしたかについて記録しておきます。</p>



<h2 class="wp-block-heading">１　<strong>乗り換えを決意した理由</strong>〜ドル建て料金は重過ぎる〜</h2>



<figure class="wp-block-image size-large is-resized"><a href="https://imakat.com/rd.php?id=2Dzrm1dc.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=2Dzrm1dc.png" alt="" style="width:311px;height:auto"/></a></figure>



<p class="wp-block-paragraph">これまでDropbox Plus（2TB）を利用していましたが、ドル建て決済（131.87 USD/年）のため近年の円安傾向も相まって年間2万円超（月換算で1,700円〜2,000円近く）の負担となっていました。</p>



<p class="wp-block-paragraph">特に個人の場合、ふつうは貿易はしてないので、支払いに充てる外貨資金があるわけでもなく、やっぱりDropboxは企業重視だなと感じます。</p>



<p class="wp-block-paragraph">そこへ、新たに登場したGoogle AI Proプランは、月額2,900円ですが、生成AIの結構幅広い機能に加えて、Dropboxの2.5倍の5TBの大容量ストレージがパッケージされています。ストレージ単価や今後の拡張性を考慮すると、Googleドライブへ集約する方が圧倒的にメリットが大きいと判断しました。</p>



<p class="wp-block-paragraph"></p>



<h2 class="wp-block-heading">２　<strong>ローカルフォルダ構造とパスの維持工夫</strong></h2>



<p class="wp-block-paragraph">M1 Mac mini環境において、各種スクリプトや自作アプリが既存の「Dropbox」フォルダパスに深く依存しています。すべてのフォルダパスを書き換えるのは膨大な作業となるため、以下の形でミラーリング同期を設定しました。</p>



<ul class="wp-block-list">
<li>ローカル側：既存のフォルダ構造（&#8230;/Dropbox）をそのまま維持</li>



<li>クラウド側：Googleドライブ上の パソコン ＞ マイ Mac mini ＞ mybox_1 フォルダに対してミラーリング同期を実施</li>
</ul>



<p class="wp-block-paragraph">この設計により、ローカル上のスクリプトやファイルパスを変更することなく、同期先クラウドだけをDropboxからGoogleドライブへ安全に切り替えることが可能になります。</p>



<h2 class="wp-block-heading">３　<strong>自作アプリ（AppSheet・配信システム）の影響調査と改善</strong></h2>



<p class="wp-block-paragraph">最も懸念していたのが、自作のメディア管理アプリ（マイライブラリ）および配信システムへの影響です。</p>



<p class="wp-block-paragraph">これまではGoogleスプレッドシート（F1_マイライブラリ）のA列に「Dropbox共有リンク」を記録し、WordPress等のエンコードURLや各配信サーバーへと連携する仕組みを構築していました。Dropboxを解約するとこれらの共有リンクが無効化されてしまうため、以下の改修を行いました。</p>



<p class="wp-block-paragraph">改善ポイント</p>



<p class="wp-block-paragraph">①Dropboxリンク依存の脱却</p>



<ul class="wp-block-list">
<li>今後の新規登録（テスト登録.png 等）では、Dropboxリンク欄を空欄に設定。</li>
</ul>



<p class="wp-block-paragraph">②ユニークID（wpidex）による配信URL生成</p>



<ul class="wp-block-list">
<li>外部共有リンクに頼らず、システム内部のユニークID（wpidex）をベースにWordPress経由の再生URLを直接生成・配信するロジックへ変更。</li>
</ul>



<p class="wp-block-paragraph">実証実験を行い、Dropboxリンクが空欄の状態でも正しくデータが表示され、動画・画像配信に一切支障がないことを確認できました。</p>



<p class="wp-block-paragraph">結局、今回の改善手順のポイントは、<br><strong>Dropboxを解除したあとは、普通のローカルフォルダだけで運用が出来るようにすること。</strong><br>次に<br><strong>そのローカルフォルダをGoogleDriveなど別の場所にミラーリング同期すること。</strong><br></p>



<p class="wp-block-paragraph">ということになります。</p>



<figure class="wp-block-image size-large is-resized"><a href="https://imakat.com/rd.php?id=6Wh31WZu.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=6Wh31WZu.png" alt="" style="aspect-ratio:3.9321096689962367;width:749px;height:auto"/></a></figure>



<h2 class="wp-block-heading">４　<strong>まとめと今後の運用</strong></h2>



<p class="wp-block-paragraph">ビデオの中では、フォルダ名にDropbox dropbox_1を残したまま、昭和歌謡の「昔の名前で出ています(小林旭)」として、使い続けることを決心していたのですが、気が変わりまして、できるなら、ローカルフォルダ名も、mybox mybox_1に改名したいと強く思うようになり、主にPythonアプリを７本程度、手直ししました。その結果、現在は、昔の名前は名乗らず、今の名前で出ています。</p>



<figure class="wp-block-image size-large is-resized"><a href="https://imakat.com/rd.php?id=SoT6Enmr.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=SoT6Enmr.png" alt="" style="aspect-ratio:1.7769094939324492;width:343px;height:auto"/></a></figure>



<a rel="noopener" target="_blank" href="https://docs.google.com/drawings/d/1INrFZ_Q8zmGtWpZq7OUuv2RKofQrlrLqYylZwLLlxSY/edit?usp=sharing"  ="">
<img decoding="async" src="https://docs.google.com/drawings/d/e/2PACX-1vSmkRqDIHqNWLvNC5vzih9yDGCf0In1GISEp-aO4ZLmZm6JMxR6Qt-fnozT8hD275HJJiThM0vnKyqH/pub?w=1920&amp;h=1440"></a>



<p class="wp-block-paragraph">自作システムの改修とデータ移行の検証が完了したため、これで懸念事項はすべて解消されました。しばらく並行運用で様子を見た後、安心してDropboxの解約手続きを進める予定です。</p>



<p class="wp-block-paragraph">長年依存してきた環境からの移行は手間がかかりますが、構造をシンプルに整理し直す良い機会となりました。同様の構成でストレージ移行を検討されている方の参考になれば幸いです。</p>



<p class="wp-block-paragraph">以上</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">29546</post-id>	</item>
		<item>
		<title>【AIと自作】マイギターコードアプリが完成！～複雑なDAWを補う「自分専用の介助ロボット」～</title>
		<link>https://imakat.com/2026/07/23/29494/</link>
		
		<dc:creator><![CDATA[imakat]]></dc:creator>
		<pubDate>Thu, 23 Jul 2026 05:25:42 +0000</pubDate>
				<category><![CDATA[音楽制作]]></category>
		<category><![CDATA[cubase]]></category>
		<category><![CDATA[DAW]]></category>
		<category><![CDATA[GarageBand]]></category>
		<category><![CDATA[MIDI]]></category>
		<guid isPermaLink="false">https://imakat.com/?p=29494</guid>

					<description><![CDATA[はじめに&#160; 曲に豊かな表情をつける方法の一つに「和音の工夫」があります。今や生成AIが自動的に曲を作ってくれる時代ですが、AIが作った曲は何度聴いても心に刺さらない…そう感じるのは私だけではないはずです。 やは [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading"><strong>はじめに</strong>&nbsp;</h2>



<p class="wp-block-paragraph">曲に豊かな表情をつける方法の一つに「和音の工夫」があります。今や生成AIが自動的に曲を作ってくれる時代ですが、AIが作った曲は何度聴いても心に刺さらない…そう感じるのは私だけではないはずです。 やはり自分で和音を工夫して音楽を作りたい。そう思ってDAW（CubaseやGarageBand）に向かうのですが、高齢になってくると、ますます複雑化するDAWの操作がどんどん億劫になってしまいます。特にDAWには「ギターの指板型エディタ」が搭載されていません。高機能なギター音源（Ample Soundなど）を使えば指板入力も可能ですが、そこへ辿り着くまでの操作すら遠く感じてしまいます。</p>



<p class="wp-block-paragraph">「複雑怪奇な装置から、自分に必要なことだけを抜き出した簡単な仕組みを作りたい。まるで介助ロボットのようなアプリが欲しい」。そんな思いから、生成AIの力を借りて、Macで動く自分専用の「マイギターコードアプリ（Python/Tkinter）」を自作しました。</p>


<div class="sc-dynamic-embed">
  <style>
  /* リンクの見た目を整える */
  .sc-dynamic-embed .sc-link-container { 
      display: flex; 
      margin-bottom: 10px; 
      flex-wrap: wrap; 
      align-items: center; 
  }
  .sc-dynamic-embed .sc-link { margin-bottom: 0; }
  .sc-dynamic-embed .sc-link a {
    font-size: 15px; 
    font-weight: normal;
    text-decoration: underline;
    color: #0073aa;
  }
  .sc-dynamic-embed .sc-link a:hover { text-decoration: none; color: #000; }
  
  @media (max-width: 500px) {
    .sc-dynamic-embed .sc-link a { font-size: 13px; }
  }

  .sc-dynamic-embed .imk-line {
      display: inline-block;
      width: 100%;
      border-radius: 2px;
      transition: background-color 0.1s;
  }

  .sc-dynamic-embed #subtitleOverlay,
  .sc-dynamic-embed #scSubtitleOverlay,
  .sc-dynamic-embed .overlay-cue,
  .sc-dynamic-embed .band {
      display: none !important;
      opacity: 0 !important;
      visibility: hidden !important;
      pointer-events: none !important;
  }
  </style>

  <div class="sc-link-container">
    <p class="sc-link">
      <a href="https://imakat.com/ds62/?drid=103" target="_blank"
         onclick="return scStopAndGo(event, this);">
        👉低画質・枠外字幕はこちら
      </a>
    </p>
  </div>

  <style>
            :root{ --dr5emd-max: 1920px; }
            #subtitleOverlay, #scSubtitleOverlay, .overlay-cue, .band { display: none !important; opacity: 0 !important; }
            .imk-line { display: inline-block; width: 100%; border-radius: 2px; transition: background-color 0.1s; }
            .dr5emd-sublist details > p{ height:200px; overflow:auto; background-color:#EDF7FF; padding:2px 6px; margin:0; box-shadow:3px 3px 4px black; position: relative; }
            .dr5emd-sublist details > summary{ padding:2px 6px; width:100%; background-color:#ddd; border:none; box-shadow:3px 3px 4px black; cursor:pointer; list-style:none; }
            
                .video-wrap{position:relative;width:100%;margin:0 auto}
                #myVideo{width:100%;height:auto;min-height:200px;display:block;background:#000;}</style><div class="dr5emd-container"><div class="video-wrap"><video id="myVideo" controls poster="https://imakat.com/rd.php?id=Gdd8CbQw.png" playsinline preload="none" style="width:100%;height:auto;">  <source src="https://imakat.com/rd.php?id=cqpz01Dy.mp4" type="video/mp4">  <track src="https://imakat.com/rd.php?id=JnBGdEPn.vtt" label="日本語" srclang="ja" kind="subtitles"></video></div><div class="dr5emd-sublist"><details translate="yes"><summary translate="yes">字幕一覧(クリック)</summary> <p translate="yes">
(<a href="#" class="imk-cue" data-seek="0:00" translate="no">00:00:00</a>) 〜マイギターコードアプリの作成〜今回は、コードはコードでも、ギターのコードの作成というテーマです。<br>
(<a href="#" class="imk-cue" data-seek="0:08" translate="no">00:00:08</a>) 曲に表情をつける方法の1つに、和音の工夫があります。<br>
(<a href="#" class="imk-cue" data-seek="0:16" translate="no">00:00:16</a>) 和音の構成も、今や生成AIが自動的に作ってくれる時代になっています。<br>
(<a href="#" class="imk-cue" data-seek="0:26" translate="no">00:00:26</a>) ただし、生成AIで作った曲は何度聴いても心に刺さらない。<br>
(<a href="#" class="imk-cue" data-seek="0:34" translate="no">00:00:34</a>) そういうふうに感じるのは、私だけではないはずです。<br>
(<a href="#" class="imk-cue" data-seek="0:41" translate="no">00:00:41</a>) 久しぶりに昔のように自分で音楽を作ろうと思ってCubaseに向かうのですが、<br>
(<a href="#" class="imk-cue" data-seek="0:49" translate="no">00:00:49</a>) こっちがどんどん高齢になって頭が弱くなっているところへ輪をかけて、<br>
(<a href="#" class="imk-cue" data-seek="0:56" translate="no">00:00:56</a>) 最近のDAWはますます複雑になってきています。<br>
(<a href="#" class="imk-cue" data-seek="1:03" translate="no">00:01:03</a>) だから、どんどん億劫になってしまう。<br>
(<a href="#" class="imk-cue" data-seek="1:08" translate="no">00:01:08</a>) この複雑怪奇な装置やアプリから、必要なことだけを抜き出して、<br>
(<a href="#" class="imk-cue" data-seek="1:15" translate="no">00:01:15</a>) 簡単な仕組みを作ることができればいいわけですね。<br>
(<a href="#" class="imk-cue" data-seek="1:21" translate="no">00:01:21</a>) だから、介護ロボットとか介助ロボットのようなものが欲しいわけです。<br>
(<a href="#" class="imk-cue" data-seek="1:28" translate="no">00:01:28</a>) それを自分で作るということです。<br>
(<a href="#" class="imk-cue" data-seek="1:32" translate="no">00:01:32</a>) そこに力を貸してくれるのが、生成AIなのですね。<br>
(<a href="#" class="imk-cue" data-seek="1:38" translate="no">00:01:38</a>) 1つ1つの音をノートと言いますけれども、<br>
(<a href="#" class="imk-cue" data-seek="1:44" translate="no">00:01:44</a>) そのノートをトラックに書き込む道具をエディターと言いますが、<br>
(<a href="#" class="imk-cue" data-seek="1:50" translate="no">00:01:50</a>) DAWはキーエディター、ドラムエディター、<br>
(<a href="#" class="imk-cue" data-seek="1:56" translate="no">00:01:56</a>) スコアエディターの3つは用意されているんですが、<br>
(<a href="#" class="imk-cue" data-seek="2:01" translate="no">00:02:01</a>) ギターの指板エディターは、<br>
(<a href="#" class="imk-cue" data-seek="2:06" translate="no">00:02:06</a>) 少なくともCubase、GarageBandには搭載されていません。<br>
(<a href="#" class="imk-cue" data-seek="2:13" translate="no">00:02:13</a>) そこで掘り下げてギター音源アプリに注目すると、有名なものにAmple Soundがあります。<br>
(<a href="#" class="imk-cue" data-seek="2:25" translate="no">00:02:25</a>) Ample Soundの中の、Strumという機能の中に、指板をポチポチ押して、<br>
(<a href="#" class="imk-cue" data-seek="2:39" translate="no">00:02:39</a>) ここのところですね。さらにストロークの長さを決めると。<br>
(<a href="#" class="imk-cue" data-seek="2:47" translate="no">00:02:47</a>) そういう仕組みがあるので、これを使うという方法はあります。<br>
(<a href="#" class="imk-cue" data-seek="2:54" translate="no">00:02:54</a>) しかし、なんか遠いんですね、そこまでが。<br>
(<a href="#" class="imk-cue" data-seek="3:00" translate="no">00:03:00</a>) 私が作りたいこと、つまり介助アプリに求めることは3点です。<br>
(<a href="#" class="imk-cue" data-seek="3:08" translate="no">00:03:08</a>) 指板を押しながら和音を作る、そのコードネームを調べる、DAWへMIDI出力する。<br>
(<a href="#" class="imk-cue" data-seek="3:20" translate="no">00:03:20</a>) こういった3点だけですね。そんな単純な仕組みを作りたいんです。<br>
(<a href="#" class="imk-cue" data-seek="3:28" translate="no">00:03:28</a>) それでアプリ作りにはいったんですが、介助アプリと言ったらいいですかね。<br>
(<a href="#" class="imk-cue" data-seek="3:39" translate="no">00:03:39</a>) マイギターコードアプリですが、これを作るのにお金はかかりません。<br>
(<a href="#" class="imk-cue" data-seek="3:46" translate="no">00:03:46</a>) Mac標準のGarageBandがあればいいです。<br>
(<a href="#" class="imk-cue" data-seek="3:52" translate="no">00:03:52</a>) 私はCubaseを使いますが、GarageBandやCubaseのことをDAWと呼びます。<br>
(<a href="#" class="imk-cue" data-seek="4:05" translate="no">00:04:05</a>) 介助アプリの機能は、指の位置からコードネームを取得する。<br>
(<a href="#" class="imk-cue" data-seek="4:16" translate="no">00:04:16</a>) 押さえた場所の音を再現する。押さえた指板の画像を作成する。<br>
(<a href="#" class="imk-cue" data-seek="4:24" translate="no">00:04:24</a>) 生成AIへコードネームの調査を依頼して取得、登録をする。<br>
(<a href="#" class="imk-cue" data-seek="4:31" translate="no">00:04:31</a>) DAWへMIDIの出力をする。以上になります。<br>
(<a href="#" class="imk-cue" data-seek="4:39" translate="no">00:04:39</a>) それでは次に、具体的にアプリの動きを説明します。<br>
(<a href="#" class="imk-cue" data-seek="4:46" translate="no">00:04:46</a>) アプリを立ち上げますと、最初は追加モードでスタートします。<br>
(<a href="#" class="imk-cue" data-seek="4:57" translate="no">00:04:57</a>) それで、指板のボタンを押すと音が出ます。<br>
(<a href="#" class="imk-cue" data-seek="5:08" translate="no">00:05:08</a>) Shiftを押しながらボタンを押すと、和音で音が出ます。<br>
(<a href="#" class="imk-cue" data-seek="5:14" translate="no">00:05:14</a>) そうやって指の位置を決めていきます。<br>
(<a href="#" class="imk-cue" data-seek="5:20" translate="no">00:05:20</a>) その指の位置を決めたら、2番目に指の位置確認ボタンを押します。<br>
(<a href="#" class="imk-cue" data-seek="5:34" translate="no">00:05:34</a>) すると、a-ki's factoryというサイトからコードネームを取得します。<br>
(<a href="#" class="imk-cue" data-seek="5:41" translate="no">00:05:41</a>) こういうサイトですね。ここのサイトのところに、<br>
(<a href="#" class="imk-cue" data-seek="5:46" translate="no">00:05:46</a>) コードネームが出るんですね。ルート順、適合順ということで出ます。<br>
(<a href="#" class="imk-cue" data-seek="5:56" translate="no">00:05:56</a>) この情報を取得して、サイト提供コードネームというところに表示します。<br>
(<a href="#" class="imk-cue" data-seek="6:06" translate="no">00:06:06</a>) 次に4番目にギターの音が出力されます。<br>
(<a href="#" class="imk-cue" data-seek="6:12" translate="no">00:06:12</a>) 5番目に、指板プレビューが表示されます。こういうやつです。<br>
(<a href="#" class="imk-cue" data-seek="6:18" translate="no">00:06:18</a>) C欄に自分調査コードネームというところがあります。というのは、<br>
(<a href="#" class="imk-cue" data-seek="6:29" translate="no">00:06:29</a>) ベース音を追加した分数コードの形で解釈ができないこともあって、<br>
(<a href="#" class="imk-cue" data-seek="6:38" translate="no">00:06:38</a>) コードネームなしとなることも結構あります。<br>
(<a href="#" class="imk-cue" data-seek="6:44" translate="no">00:06:44</a>) そこで、生成AIのGemを使って、分数コードを含めたコードネームを解析してもらいます。<br>
(<a href="#" class="imk-cue" data-seek="6:54" translate="no">00:06:54</a>) Gemに貼り付ける画像は、指板プレビューに出てくるものをスクショして、<br>
(<a href="#" class="imk-cue" data-seek="7:04" translate="no">00:07:04</a>) ここにペーストするのが楽です。<br>
(<a href="#" class="imk-cue" data-seek="7:11" translate="no">00:07:11</a>) そしてその結果が、このように表示されます。<br>
(<a href="#" class="imk-cue" data-seek="7:17" translate="no">00:07:17</a>) その中身を見て、必要に応じてC欄に書き込みます。<br>
(<a href="#" class="imk-cue" data-seek="7:24" translate="no">00:07:24</a>) それでは次に変更モードに入ります。変更モードを選ぶと、<br>
(<a href="#" class="imk-cue" data-seek="7:32" translate="no">00:07:32</a>) 画像ファイルの選択を通じてコードが選択できるようになって、<br>
(<a href="#" class="imk-cue" data-seek="7:39" translate="no">00:07:39</a>) このC欄の修正だとか、さらにMIDIの出力ができるようになります。<br>
(<a href="#" class="imk-cue" data-seek="7:48" translate="no">00:07:48</a>) 一覧のサムネイルから、あるいはリストから、指板画像ファイルを選ぶことができます。<br>
(<a href="#" class="imk-cue" data-seek="8:00" translate="no">00:08:00</a>) サムネイル型ですけれども、残念ながらこのサムネイルの拡大が、<br>
(<a href="#" class="imk-cue" data-seek="8:08" translate="no">00:08:08</a>) MacOS標準では困難ということで、<br>
(<a href="#" class="imk-cue" data-seek="8:14" translate="no">00:08:14</a>) この場所では少し我慢するしかないです。<br>
(<a href="#" class="imk-cue" data-seek="8:20" translate="no">00:08:20</a>) 音楽制作を続けている中でしたら、その画像がそれほど遠くない場所に、<br>
(<a href="#" class="imk-cue" data-seek="8:27" translate="no">00:08:27</a>) 固まって近くにあると思うので、欲しいサムネイルが見つかるはずです。<br>
(<a href="#" class="imk-cue" data-seek="8:34" translate="no">00:08:34</a>) それほどのストレスにはならないと思います。<br>
(<a href="#" class="imk-cue" data-seek="8:39" translate="no">00:08:39</a>) コードネームを指定する時の悩みですけれども、<br>
(<a href="#" class="imk-cue" data-seek="8:45" translate="no">00:08:45</a>) 前に作ったあの和音はどこにある？と探すのが結構面倒ですよね。<br>
(<a href="#" class="imk-cue" data-seek="8:51" translate="no">00:08:51</a>) コードネームからの音の表情はなんとか連想はできるのですが、<br>
(<a href="#" class="imk-cue" data-seek="8:57" translate="no">00:08:57</a>) 指板の方が音の表情は、より豊かに連想ができます。<br>
(<a href="#" class="imk-cue" data-seek="9:03" translate="no">00:09:03</a>) 結局、実際に指を置いて出せる音なので、<br>
(<a href="#" class="imk-cue" data-seek="9:10" translate="no">00:09:10</a>) 指が疲れる押さえ方だなとか、体の動きと出てくる音が、<br>
(<a href="#" class="imk-cue" data-seek="9:17" translate="no">00:09:17</a>) 自分の脳のどこかに記憶されているわけですね。<br>
(<a href="#" class="imk-cue" data-seek="9:24" translate="no">00:09:24</a>) そこで単発的な使い方にはなるんですけれども、ご覧のFinderから(サムネイルが拡大できます！)、<br>
(<a href="#" class="imk-cue" data-seek="9:32" translate="no">00:09:32</a>) 指板画像のファイルの入っているフォルダを開いて、<br>
(<a href="#" class="imk-cue" data-seek="9:38" translate="no">00:09:38</a>) その中からファイルを選んで、クイックアクションから、<br>
(<a href="#" class="imk-cue" data-seek="9:45" translate="no">00:09:45</a>) このマイギターコード起動および試聴のアプリをクリックします。<br>
(<a href="#" class="imk-cue" data-seek="9:52" translate="no">00:09:52</a>) 立ち上げ以降の操作は全く同じです。<br>
(<a href="#" class="imk-cue" data-seek="9:58" translate="no">00:09:58</a>) あとそれから、このMIDI出力用のボタンを押しますと、<br>
(<a href="#" class="imk-cue" data-seek="10:06" translate="no">00:10:06</a>) フォルダが開いてMIDIファイルがハイライトされますので、<br>
(<a href="#" class="imk-cue" data-seek="10:13" translate="no">00:10:13</a>) これをDAWのトラックにドラッグアンドドロップします。<br>
(<a href="#" class="imk-cue" data-seek="10:19" translate="no">00:10:19</a>) こういった形でフォルダが開きますので、<br>
(<a href="#" class="imk-cue" data-seek="10:23" translate="no">00:10:23</a>) このtemp_chord.midファイルをトラックにドラッグアンドドロップします。<br>
(<a href="#" class="imk-cue" data-seek="10:33" translate="no">00:10:33</a>) これはGarageBandへドロップしたMIDIノートです。<br>
(<a href="#" class="imk-cue" data-seek="10:38" translate="no">00:10:38</a>) それから次に、これはCubaseにドロップしたMIDIノートです。<br>
(<a href="#" class="imk-cue" data-seek="10:47" translate="no">00:10:47</a>) 以上で全体の説明は終わりですが、このアプリを使って、<br>
(<a href="#" class="imk-cue" data-seek="10:54" translate="no">00:10:54</a>) Cubaseに出力した音を再生してみます。<br>
(<a href="#" class="imk-cue" data-seek="11:40" translate="no">00:11:40</a>) まだまだ改善点は出てくると思いますけれども、ひとまずは完成にしておきます。<br>
</p> </details>
<style>
/* 汎用ベーススタイル（マイライブラリ等の単独表示・ブログ埋め込み用） */
details { font: 16px "Open Sans", Calibri, sans-serif; width: 100%; }
details > summary { padding: 2px 6px; width: 100%; background-color: #ddd; border: none; box-shadow: 3px 3px 4px black; cursor: pointer; list-style: none; }
details > p { font: 14px "Open Sans", Calibri, sans-serif; height:150px; overflow: scroll; background-color: #EDF7FF; padding: 2px 6px; margin: 0; box-shadow: 3px 3px 4px black; }

/* 動画パッケージ（mu1emd, dr5, dr6等）内での上書き専用スタイル */
.mu1emd-sublist details > p, .dr5-sublist details > p, .dr6-sublist details > p {
    display: block;
    min-height: 180px;
    max-height: 240px;
    overflow-y: auto;
    background-color: #EDF7FF;
    padding: 6px 8px;
    margin: 0;
    box-shadow: 3px 3px 4px black;
    border-radius: 0 0 5px 5px;
}
</style>
</div></div>            <script>
            document.addEventListener("DOMContentLoaded", function(){
                var video = document.getElementById("myVideo");
                if(!video) return;

                            });
            </script>
            

  <script>
  (function(){
    var me = document.currentScript;
    var wrapper = me ? me.closest('.sc-dynamic-embed') : null;

    function protectVideo() {
      var target = wrapper ? wrapper : document;
      var mediaEls = target.querySelectorAll('video, audio');
      mediaEls.forEach(function(v){
        if(v.dataset.protected === 'true') return;
        v.dataset.protected = 'true';
        v.setAttribute('controlsList', 'nodownload');
        v.oncontextmenu = function() { return false; };
        v.addEventListener('contextmenu', function(e){ e.preventDefault(); return false; }, false);
      });
    }

    function initSubtitles() {
      var target = wrapper ? wrapper : document;
      var video = target.querySelector('video, audio');
      var listContainer = target.querySelector('details > p');
      
      if (!video) return false; 
      
      if (video.dataset.subInit === 'true') return true; 
      video.dataset.subInit = 'true';

      var oldOverlay = target.querySelector('#subtitleOverlay') || target.querySelector('#scSubtitleOverlay');
      if (oldOverlay) {
          oldOverlay.style.setProperty('display', 'none', 'important');
          oldOverlay.innerHTML = ''; 
      }

      function isSpecialMode() {
        var isFs = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement);
        var isPiP = !!(document.pictureInPictureElement && document.pictureInPictureElement === video) || (video.webkitPresentationMode === "picture-in-picture");
        var isIOSFs = !!video.webkitDisplayingFullscreen; 
        return isFs || isPiP || isIOSFs;
      }

      // 初期状態のセット（リストがなければ最初から showing に）
      try {
        if(video.textTracks && video.textTracks.length > 0){
          for(var i=0; i<video.textTracks.length; i++){
             if(video.textTracks[i].kind === 'subtitles' || video.textTracks[i].kind === 'captions'){
                 video.textTracks[i].mode = (!listContainer) ? "showing" : "hidden";
             }
          }
        }
      } catch(e){}

      // 字幕一覧が存在する場合のみ、リストのフォーマット処理を行う
      if (listContainer) {
          var detailsEl = target.querySelector("details");
          if (detailsEl) {
              detailsEl.open = true; 
              var summaryEl = detailsEl.querySelector("summary");
              if (summaryEl) summaryEl.textContent = "字幕(シーン)はここをクリック";
          }

          if (!listContainer.dataset.formatted) {
              var html = listContainer.innerHTML;
              var lines = html.split(/<br\s*\/?>/i);
              var newHtml = "";
              for(var j=0; j<lines.length; j++) {
                  if(lines[j].trim() === "") continue;
                  newHtml += "<span class='imk-line'>" + lines[j] + "</span><br>";
              }
              listContainer.innerHTML = newHtml;
              listContainer.dataset.formatted = "true";
          }

          var rootSublist = target.querySelector(".dr5-sublist") || listContainer.parentElement;
          if (rootSublist) {
            rootSublist.addEventListener("click", function(e){
              var a = e.target.closest && e.target.closest("a.imk-cue[data-seek]");
              if(!a) return;
              e.preventDefault();
              var ts = a.getAttribute("data-seek");
              if(!ts) return;
              var p = ts.trim().split(":").map(function(x){return parseInt(x,10)||0;});
              var sec = (p.length===2) ? p[0]*60+p[1] : (p.length===3 ? p[0]*3600+p[1]*60+p[2] : null);
              if(sec==null) return;
              try{ video.currentTime = sec; if(video.paused) video.play(); }catch(_){}
            });
          }
      }

      // ★ dr52.php と全く同じ「常時監視」のロジック
      // これにより、Safariが読み込み遅延を起こしても確実にONになります
      video.addEventListener("timeupdate", function(){
        var hasList = (listContainer !== null);
        var desiredMode = (isSpecialMode() || !hasList) ? "showing" : "hidden";
        
        try {
            if(video.textTracks && video.textTracks.length > 0){
                for(var i=0; i<video.textTracks.length; i++){
                    if((video.textTracks[i].kind === 'subtitles' || video.textTracks[i].kind === 'captions') && video.textTracks[i].mode !== desiredMode) {
                        video.textTracks[i].mode = desiredMode;
                    }
                }
            }
        } catch(e){}

        // リストがない場合はこれ以降のハイライト処理をスキップ
        if (!hasList) return;

        var cues = listContainer.querySelectorAll("a.imk-cue");
        if(cues.length === 0) return;
        var cur = video.currentTime;
        var activeA = null;

        for(var i=0; i<cues.length; i++){
            var ts = cues[i].getAttribute("data-seek");
            if(!ts) continue;
            var p = ts.trim().split(":").map(function(x){return parseInt(x,10)||0;});
            var t = (p.length===2) ? p[0]*60+p[1] : (p.length===3 ? p[0]*3600+p[1]*60+p[2] : null);
            if(t !== null && cur >= t - 0.5){ activeA = cues[i]; } 
            else if(t > cur){ break; }
        }

        if(activeA){
            var activeLine = activeA.closest(".imk-line");
            if(!activeLine) activeLine = activeA;

            if(activeLine.classList.contains("active-hl")) return;

            var allLines = listContainer.querySelectorAll(".imk-line");
            for(var k=0; k<allLines.length; k++) {
                allLines[k].classList.remove("active-hl");
                allLines[k].removeAttribute("style"); 
            }
            var allLinks = listContainer.querySelectorAll("a");
            for(var m=0; m<allLinks.length; m++) {
                allLinks[m].classList.remove("active-hl");
                allLinks[m].removeAttribute("style"); 
            }

            activeLine.classList.add("active-hl");
            activeLine.style.setProperty("background-color", "#ffff00", "important");
            activeLine.style.setProperty("color", "red", "important");
            activeLine.style.setProperty("font-weight", "normal", "important");
            
            var newLinks = activeLine.querySelectorAll("a");
            for(var n=0; n<newLinks.length; n++) {
                newLinks[n].style.setProperty("color", "red", "important");
                newLinks[n].style.setProperty("text-decoration", "none", "important");
            }

            if(listContainer.offsetParent !== null){
                var containerRect = listContainer.getBoundingClientRect();
                var activeRect = activeLine.getBoundingClientRect();
                var targetScroll = listContainer.scrollTop + (activeRect.top - containerRect.top) - (listContainer.clientHeight / 2) + (activeLine.clientHeight / 2);
                listContainer.scrollTo({ top: targetScroll, behavior: "smooth" });
            }
        }
      });

      return true;
    }

    var checks = 0;
    var checkTimer = setInterval(function(){
      protectVideo();
      var success = initSubtitles();
      checks++;
      if (success || checks > 20) { 
        clearInterval(checkTimer);
      }
    }, 500); 

    if (!window.scStopAndGo) {
      window.scStopAndGo = function(event, link){
        try{
          var mediaEls = document.querySelectorAll('video, audio');
          mediaEls.forEach(function(m){
            try{
              if (!m.paused) m.pause();
              if (document.pictureInPictureElement === m && document.exitPictureInPicture) {
                document.exitPictureInPicture().catch(function(){});
              }
            }catch(e){}
          });
        }finally{
          event.preventDefault();
          setTimeout(function(){
            if (link.target === '_blank') {
              window.open(link.href, '_blank');
            } else {
              window.location.href = link.href;
            }
          }, 50);
        }
        return false;
      };
    }
  })();
  </script>
</div>



<div class="wp-block-cocoon-blocks-toggle-box-1 toggle-wrap toggle-box block-box not-nested-style cocoon-block-toggle"><input id="toggle-checkbox-202608130804050" class="toggle-checkbox" type="checkbox"/><label class="toggle-button" for="toggle-checkbox-202608130804050"><strong><span class="marker">実装方法(クリック)</span></strong></label><div class="toggle-content">
<figure class="wp-block-flexible-table-block-table"><table class="has-fixed-layout"><tbody><tr><td>要約：「マイギターコード」は、ギターコードの管理と自動化を行うPython製Macアプリです。ローカル上のExcel DBと連携し、画像選択時に指位置データやコードネームを自動表示します。Playwrightをヘッドレスで利用し、バックグラウンドでWebからコード情報を取得・自動同期。MacのGUIバグ回避のため<code>tk.Entry</code>を採用しています。ローカルMIDI機能が充実しており、<code>mido</code>仮想ポート経由でGarageBand/Cubase等のDAWへ和音や単音を発音。アルペジオ再生とMIDIファイル生成、Finderでのハイライト表示も可能です。オートセーブ、Excel行と画像ファイルの物理削除に対応。画像パスを引数に受け取り、自動モード切替・DB読込・MIDI発音を可能にする外部連携エンジンを搭載。Stream DeckやAutomatorのクイックアクションからの起動に対応し、クイックアクションでは既存アプリを終了しGarageBand/Cubase起動後に指定画像でアプリが立ち上がります。</td></tr><tr><td>実装方法：<br>(1)下のボタンからtxtファイルをダウンロードしてお使いの生成AI(Gemini ChatGPT Claudeなど)のチャットにペーストしてください。<br>(2)以下の指示文をチャットにペーストしてください。<br>「添付したスクリプトは、特定の環境向けに作られた[ ギターコードの管理と自動化を行う ]プログラムです。<br>これを私のパソコン環境（例：Windows 11 / OneDriveを使用）に移植して、今日から全く同じ動作を実行できるようにしたいと思います。<br>①私の環境に適合するように、プログラム（例：PowerShellやPythonなど最適な言語）を、必要に応じて、新しく書き換えて全文出力してください。<br>②プログラミング初心者でも迷わずに、今日から私のPCでこのプログラムを起動・実装するための「具体的な手順書」をわかりやすくテキストで教えてください。以上」</td></tr><tr><td><a href="https://imakat.com/script_list/?pubtxt=ギターコードアプリ改良版_pub.txt" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=zrU95MiY.png" alt="" style="width:120px; height:auto;"></a></td></tr><tr><td>内容に関するお問い合わせは以下へ↓</td></tr><tr><td><a rel="noopener" target="_blank" href="https://notebooklm.google.com/notebook/aa64cc3d-0160-4433-b934-ecc5ab902287"><img decoding="async" src="https://imakat.com/rd.php?id=vZJJ3Jfw.png" alt="" style="width:200px; height:auto;"></a></td></tr></tbody></table></figure>
</div></div>



<h2 class="wp-block-heading"><strong>介助アプリに求めた「3つの必須機能」</strong>&nbsp;</h2>



<p class="wp-block-paragraph">このアプリの目的は非常にシンプルです。以下の3つのステップで、音楽制作をサポートします。</p>



<p class="wp-block-paragraph"><strong>ステップ１：指板を押しながら和音を作る（本物のギター感覚）</strong>&nbsp;</p>



<p class="wp-block-paragraph">アプリ上の指板をクリックすると、まるで本物のギターの弦を押さえたようにその「単音」が即座に鳴ります。「Shiftキーを押しながらクリック」すると、全体の「和音」がジャラーンと鳴ります。画面が固まることもなくサクサクと音を探れるため、直感的にコードの響きを作っていくことができます。</p>



<p class="wp-block-paragraph"><strong>ステップ２：コードネームを調べる（自動取得＆AI解析）</strong>&nbsp;</p>



<p class="wp-block-paragraph">指の位置が決まったら、「指位置確認」ボタンを押します。すると、裏側で外部サイトと通信し、適合するコードネームを自動で取得してくれます。 しかし、ベース音を追加した分数コードなど、既存のサイトでは「コードネームなし」と判定されてしまうこともあります。そんな時は、アプリ上の指板画像をコピーして生成AI（Gem）に解析を依頼します。「最も一般的な解釈としては E13sus4、または分数コードの Bm11/E です」といった回答が得られるので、それを自分専用のコードネームとして登録しておきます。</p>



<p class="wp-block-paragraph"><strong>ステップ３：DAWへMIDI出力する（ドラッグ＆ドロップ連携）</strong>&nbsp;</p>



<p class="wp-block-paragraph">作った和音をCubaseやGarageBandなどのDAWで使いたい時、「MIDI出力」ボタンを押すと、自動生成されたMIDIファイル（.mid）がMacのFinderでハイライト表示されて手前にポップアップします。あとはそれをDAWのトラックへ直接ドラッグ＆ドロップするだけ。クリップボードの制約を受けない、確実でストレスフリーな連携です。</p>



<figure class="wp-block-image size-large"><a href="https://imakat.com/rd.php?id=4PNyQFHQ.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=4PNyQFHQ.png" alt=""/></a></figure>



<h2 class="wp-block-heading"><strong>使い勝手を向上させる裏側のインフラ設計</strong>&nbsp;</h2>



<p class="wp-block-paragraph">さらに、過去に作ったコードを効率よく再利用するための「インフラ設計」にもこだわりました。</p>



<ul class="wp-block-list">
<li><strong>右クリック一発で画像ロード＆全自動発音</strong>&nbsp;</li>
</ul>



<p class="wp-block-paragraph">「前に作ったあの和音、どんな響きだったかな？」と思った時、わざわざアプリを立ち上げる必要はありません。Finderから直接動かします。<strong>Finderはサムネイルを見やすいように拡大できます</strong>。それで、Finderに並んだ指板画像（サムネイル）を右クリックし、自作のクイックアクションを選ぶだけ。裏側でDAWが立ち上がり、アプリが該当画像を読み込んでフワッと起動し、全自動でギターが「ジャラーン」と鳴る流麗な連携を実現しました。<br></p>



<ul class="wp-block-list">
<li><strong>ファイル名がそのまま音符に！データとMIDIの直結設計</strong><br>例えば guitar_fretboard_30-25-22-18-14-n.jpg というファイル名。この「30-25-22-18-14-n」という文字列は、1弦から6弦までの指板の絶対値を表しています。アプリはファイル名からこの文字列を抽出した瞬間、「絶対値＋35＝MIDIノートナンバー」というシンプルな計算式で、即座にMIDIデータへ変換します。これにより高速で音が鳴ります。<br></li>



<li><strong>意識させないデータベース自動同期</strong><br>変更を加えたり別の画像を選んだ瞬間に、画面と裏側のExcelデータベースが100%自動で同期します。いちいち「保存ボタン」を押す手間を省くオートセーブ機能を搭載しました。<br></li>
</ul>



<h2 class="wp-block-heading"><strong>開発の裏側：AIバケツリレー</strong></h2>



<p class="wp-block-paragraph">このアプリはPythonで600行を超える大規模なものですが、すべて自分でプログラミングしたわけではありません。私が構築している「AIバケツリレー」が役に立ちました。 まずNotebookでシステム全体の作戦を練り、設計図を書かせます。そして、いざ大規模なコードを生成する際は、Google AI Studioという「絶対にコードを省略しない重装備のAI」へ指示書を渡して一括で出力させました。適材適所でAIを使い分けるこのエコシステムのおかげで、複雑なアプリも無事完成に漕ぎ着けました。</p>



<h2 class="wp-block-heading"><strong>おわりに</strong></h2>



<p class="wp-block-paragraph">歳を重ねるにつれ、既存の高機能すぎるツールに合わせるのが辛くなることがあります。しかし、AIをパートナーに据えることで、必要な機能だけを抽出した「自分専用の介助ロボット」を自作できるようになりました。 このアプリによって、再び楽しく音楽制作に向かうことができればいいのですが、さあどうかな。</p>



<p class="wp-block-paragraph">以上</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">29494</post-id>	</item>
		<item>
		<title>【Notebook活用】第３弾、日々の備忘録を全自動でAIの頭脳へ還流させるエコシステムを作った</title>
		<link>https://imakat.com/2026/06/29/29358/</link>
		
		<dc:creator><![CDATA[imakat]]></dc:creator>
		<pubDate>Mon, 29 Jun 2026 05:32:44 +0000</pubDate>
				<category><![CDATA[WordPress]]></category>
		<category><![CDATA[デジタル]]></category>
		<category><![CDATA[ものづくり]]></category>
		<category><![CDATA[NotebookLM]]></category>
		<guid isPermaLink="false">https://imakat.com/?p=29358</guid>

					<description><![CDATA[2026.7.20更新：NotebookLMの名称が、Gemini Notebookに変更になっています。 はじめに 前回、生成AIを活用したコード生成システム（第２弾）をご紹介しましたが、今回はその仕組みをさらに拡張し [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">2026.7.20更新：NotebookLMの名称が、Gemini Notebookに変更になっています。</p>



<h2 class="wp-block-heading"><strong>はじめに</strong> </h2>



<p class="wp-block-paragraph">前回、生成AIを活用したコード生成システム（第２弾）をご紹介しましたが、今回はその仕組みをさらに拡張した「第３弾」のお話です。 AIとの高度な対話メモや、Webで見つけた有益な情報、日々の備忘録を、いかにして無駄なく・手間なくNotebook（あるいはカスタムGem）の知識ソースへ送り込み、自分のAIを賢く育てていくか。今回は、その進化のループを回す、私なりの最強インフラをご紹介します。</p>


<div class="sc-dynamic-embed">
  <style>
  /* リンクの見た目を整える */
  .sc-dynamic-embed .sc-link-container { 
      display: flex; 
      margin-bottom: 10px; 
      flex-wrap: wrap; 
      align-items: center; 
  }
  .sc-dynamic-embed .sc-link { margin-bottom: 0; }
  .sc-dynamic-embed .sc-link a {
    font-size: 15px; 
    font-weight: normal;
    text-decoration: underline;
    color: #0073aa;
  }
  .sc-dynamic-embed .sc-link a:hover { text-decoration: none; color: #000; }
  
  @media (max-width: 500px) {
    .sc-dynamic-embed .sc-link a { font-size: 13px; }
  }

  .sc-dynamic-embed .imk-line {
      display: inline-block;
      width: 100%;
      border-radius: 2px;
      transition: background-color 0.1s;
  }

  .sc-dynamic-embed #subtitleOverlay,
  .sc-dynamic-embed #scSubtitleOverlay,
  .sc-dynamic-embed .overlay-cue,
  .sc-dynamic-embed .band {
      display: none !important;
      opacity: 0 !important;
      visibility: hidden !important;
      pointer-events: none !important;
  }
  </style>

  <div class="sc-link-container">
    <p class="sc-link">
      <a href="https://imakat.com/ds62/?drid=100" target="_blank"
         onclick="return scStopAndGo(event, this);">
        👉低画質・枠外字幕はこちら
      </a>
    </p>
  </div>

  <style>
            :root{ --dr5emd-max: 1920px; }
            #subtitleOverlay, #scSubtitleOverlay, .overlay-cue, .band { display: none !important; opacity: 0 !important; }
            .imk-line { display: inline-block; width: 100%; border-radius: 2px; transition: background-color 0.1s; }
            .dr5emd-sublist details > p{ height:200px; overflow:auto; background-color:#EDF7FF; padding:2px 6px; margin:0; box-shadow:3px 3px 4px black; position: relative; }
            .dr5emd-sublist details > summary{ padding:2px 6px; width:100%; background-color:#ddd; border:none; box-shadow:3px 3px 4px black; cursor:pointer; list-style:none; }
            
                .video-wrap{position:relative;width:100%;margin:0 auto}
                #myVideo{width:100%;height:auto;min-height:200px;display:block;background:#000;}</style><div class="dr5emd-container"><div class="video-wrap"><video id="myVideo" controls poster="https://imakat.com/rd.php?id=JL43fs90.png" playsinline preload="none" style="width:100%;height:auto;">  <source src="https://imakat.com/rd.php?id=lnUK0wRC.mp4" type="video/mp4">  <track src="https://imakat.com/rd.php?id=7sAZ6pz9.vtt" label="日本語" srclang="ja" kind="subtitles"></video></div><div class="dr5emd-sublist"><details translate="yes"><summary translate="yes">字幕一覧(クリック)</summary> <p translate="yes">
(<a href="#" class="imk-cue" data-seek="0:00" translate="no">00:00:00</a>) ~Notebookへ備忘録の反映〜NotebookLM活用ということで、<br>
(<a href="#" class="imk-cue" data-seek="0:04" translate="no">00:00:04</a>) 日々の備忘録を全自動で<br>
(<a href="#" class="imk-cue" data-seek="0:08" translate="no">00:00:08</a>) AIの頭にですね、還流させる仕組み、<br>
(<a href="#" class="imk-cue" data-seek="0:12" translate="no">00:00:12</a>) エコシステムを作りましたので紹介します。<br>
(<a href="#" class="imk-cue" data-seek="0:16" translate="no">00:00:16</a>) 「君だけの天才AI相棒を作る魔法の仕組み」なんて書かれてますが、<br>
(<a href="#" class="imk-cue" data-seek="0:22" translate="no">00:00:22</a>) まあ、それほど大げさなもんではないです。<br>
(<a href="#" class="imk-cue" data-seek="0:26" translate="no">00:00:26</a>) 現状の課題を整理しますと、<br>
(<a href="#" class="imk-cue" data-seek="0:30" translate="no">00:00:30</a>) AIの頭の中っていうのはですね、<br>
(<a href="#" class="imk-cue" data-seek="0:36" translate="no">00:00:36</a>) 人間があれこれ情報取ってきては、そのまま勝手にどんどん中に放り込むのでですね、<br>
(<a href="#" class="imk-cue" data-seek="0:44" translate="no">00:00:44</a>) すぐにゴミ屋敷状態になってしまいます。<br>
(<a href="#" class="imk-cue" data-seek="0:48" translate="no">00:00:48</a>) 一度に読み込める数には制限があるので、<br>
(<a href="#" class="imk-cue" data-seek="0:52" translate="no">00:00:52</a>) すぐ限界が来る、容量オーバーということが起きます。<br>
(<a href="#" class="imk-cue" data-seek="0:59" translate="no">00:00:59</a>) またあとですね、<br>
(<a href="#" class="imk-cue" data-seek="1:03" translate="no">00:01:03</a>) NotebookLMにつけるソースはですね、フォルダごとに設定ができないんですね。<br>
(<a href="#" class="imk-cue" data-seek="1:12" translate="no">00:01:12</a>) 残念ながら1枚ずつGoogleドキュメントとかスプレッドシートなどをですね、1枚ずつ手作業で登録するということになります。<br>
(<a href="#" class="imk-cue" data-seek="1:23" translate="no">00:01:23</a>) これが結構超めんどくさいということもあります。<br>
(<a href="#" class="imk-cue" data-seek="1:28" translate="no">00:01:28</a>) それから、そのソースに登録するチャットをした場合ですね、「メモに保存」というのを選ぶんですが、<br>
(<a href="#" class="imk-cue" data-seek="1:39" translate="no">00:01:39</a>) 残念ながらですね、その仕組みがちょっとまだ中途半端で、<br>
(<a href="#" class="imk-cue" data-seek="1:48" translate="no">00:01:48</a>) 質問者の質問内容がですね、残らないんですね。<br>
(<a href="#" class="imk-cue" data-seek="1:55" translate="no">00:01:55</a>) AIからの回答、それがですね、保存されるんです。<br>
(<a href="#" class="imk-cue" data-seek="2:02" translate="no">00:02:02</a>) だから、後で見返した時に、自分がどんな質問をしたことに対しての返答かという関係が分からなくなるんですね。<br>
(<a href="#" class="imk-cue" data-seek="2:11" translate="no">00:02:11</a>) これは非常によろしくないです。<br>
(<a href="#" class="imk-cue" data-seek="2:15" translate="no">00:02:15</a>) そこで、あの「メモに保存」というのは使わなくて、<br>
(<a href="#" class="imk-cue" data-seek="2:21" translate="no">00:02:21</a>) チャットで表示された自分の質問とAIの返答を全てコピーして、それをテキストファイルに貼り付ける。<br>
(<a href="#" class="imk-cue" data-seek="2:36" translate="no">00:02:36</a>) で、その内容をさらに要約すると。それを今作ったテキストファイルに貼り付けるということをします。<br>
(<a href="#" class="imk-cue" data-seek="2:46" translate="no">00:02:46</a>) それをパソコンにある専用のフォルダに放り込む。note_sumといったようなフォルダに放り込むわけです。<br>
(<a href="#" class="imk-cue" data-seek="2:58" translate="no">00:02:58</a>) そうすると、その後Pythonを中心としたシステムが動いて、それをですね、まとめてGoogleドキュメントの方に送り込むということをやります。<br>
(<a href="#" class="imk-cue" data-seek="3:11" translate="no">00:03:11</a>) で、そのGoogleドキュメントですけれど、今10本のファイルにしてありまして、<br>
(<a href="#" class="imk-cue" data-seek="3:18" translate="no">00:03:18</a>) その1つのGoogleドキュメントの中に、今30個のテキストファイルが入るように設定してあります。<br>
(<a href="#" class="imk-cue" data-seek="3:29" translate="no">00:03:29</a>) で、その中身はですね、最新順にどんどん自動アップデートされるようになっていて、<br>
(<a href="#" class="imk-cue" data-seek="3:37" translate="no">00:03:37</a>) 常に新しいものから順番に読み込むというようなですね、仕組みになってます。<br>
(<a href="#" class="imk-cue" data-seek="3:45" translate="no">00:03:45</a>) 何分にもAIっていうのは、まだまだ頭が良くなくて、一番最新に読み込んだ情報をよく覚えているということになりますので、<br>
(<a href="#" class="imk-cue" data-seek="3:55" translate="no">00:03:55</a>) 今はそういった使い方をするしかないという状況です。<br>
(<a href="#" class="imk-cue" data-seek="4:00" translate="no">00:04:00</a>) 本の中身だけを最新順に自動アップデートすると。<br>
(<a href="#" class="imk-cue" data-seek="4:05" translate="no">00:04:05</a>) 「断捨離も連動」って書かれてますけども、要はあの、<br>
(<a href="#" class="imk-cue" data-seek="4:12" translate="no">00:04:12</a>) このnote_sumフォルダからですね、テキストファイルを削除すれば、自動的にGoogleドキュメントの中身も削除されるという関係になります。<br>
(<a href="#" class="imk-cue" data-seek="4:27" translate="no">00:04:27</a>) ま、そんなような形でGoogleドキュメントが整理されるので、それをNotebookにリンクさせておく。<br>
(<a href="#" class="imk-cue" data-seek="4:36" translate="no">00:04:36</a>) そうすれば、新しい情報をですね、追加すると<br>
(<a href="#" class="imk-cue" data-seek="4:42" translate="no">00:04:42</a>) ほぼ即座にNotebookLMが<br>
(<a href="#" class="imk-cue" data-seek="4:48" translate="no">00:04:48</a>) 新しい情報を取得できるようになるという関係です。<br>
(<a href="#" class="imk-cue" data-seek="4:53" translate="no">00:04:53</a>) そんなところのですね、仕組みを新たに追加しました。<br>
(<a href="#" class="imk-cue" data-seek="4:59" translate="no">00:04:59</a>) 以上です。<br>
</p> </details>
<style>
/* 汎用ベーススタイル（マイライブラリ等の単独表示・ブログ埋め込み用） */
details { font: 16px "Open Sans", Calibri, sans-serif; width: 100%; }
details > summary { padding: 2px 6px; width: 100%; background-color: #ddd; border: none; box-shadow: 3px 3px 4px black; cursor: pointer; list-style: none; }
details > p { font: 14px "Open Sans", Calibri, sans-serif; height:150px; overflow: scroll; background-color: #EDF7FF; padding: 2px 6px; margin: 0; box-shadow: 3px 3px 4px black; }

/* 動画パッケージ（mu1emd, dr5, dr6等）内での上書き専用スタイル */
.mu1emd-sublist details > p, .dr5-sublist details > p, .dr6-sublist details > p {
    display: block;
    min-height: 180px;
    max-height: 240px;
    overflow-y: auto;
    background-color: #EDF7FF;
    padding: 6px 8px;
    margin: 0;
    box-shadow: 3px 3px 4px black;
    border-radius: 0 0 5px 5px;
}
</style>
</div></div>            <script>
            document.addEventListener("DOMContentLoaded", function(){
                var video = document.getElementById("myVideo");
                if(!video) return;

                            });
            </script>
            

  <script>
  (function(){
    var me = document.currentScript;
    var wrapper = me ? me.closest('.sc-dynamic-embed') : null;

    function protectVideo() {
      var target = wrapper ? wrapper : document;
      var mediaEls = target.querySelectorAll('video, audio');
      mediaEls.forEach(function(v){
        if(v.dataset.protected === 'true') return;
        v.dataset.protected = 'true';
        v.setAttribute('controlsList', 'nodownload');
        v.oncontextmenu = function() { return false; };
        v.addEventListener('contextmenu', function(e){ e.preventDefault(); return false; }, false);
      });
    }

    function initSubtitles() {
      var target = wrapper ? wrapper : document;
      var video = target.querySelector('video, audio');
      var listContainer = target.querySelector('details > p');
      
      if (!video) return false; 
      
      if (video.dataset.subInit === 'true') return true; 
      video.dataset.subInit = 'true';

      var oldOverlay = target.querySelector('#subtitleOverlay') || target.querySelector('#scSubtitleOverlay');
      if (oldOverlay) {
          oldOverlay.style.setProperty('display', 'none', 'important');
          oldOverlay.innerHTML = ''; 
      }

      function isSpecialMode() {
        var isFs = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement);
        var isPiP = !!(document.pictureInPictureElement && document.pictureInPictureElement === video) || (video.webkitPresentationMode === "picture-in-picture");
        var isIOSFs = !!video.webkitDisplayingFullscreen; 
        return isFs || isPiP || isIOSFs;
      }

      // 初期状態のセット（リストがなければ最初から showing に）
      try {
        if(video.textTracks && video.textTracks.length > 0){
          for(var i=0; i<video.textTracks.length; i++){
             if(video.textTracks[i].kind === 'subtitles' || video.textTracks[i].kind === 'captions'){
                 video.textTracks[i].mode = (!listContainer) ? "showing" : "hidden";
             }
          }
        }
      } catch(e){}

      // 字幕一覧が存在する場合のみ、リストのフォーマット処理を行う
      if (listContainer) {
          var detailsEl = target.querySelector("details");
          if (detailsEl) {
              detailsEl.open = true; 
              var summaryEl = detailsEl.querySelector("summary");
              if (summaryEl) summaryEl.textContent = "字幕(シーン)はここをクリック";
          }

          if (!listContainer.dataset.formatted) {
              var html = listContainer.innerHTML;
              var lines = html.split(/<br\s*\/?>/i);
              var newHtml = "";
              for(var j=0; j<lines.length; j++) {
                  if(lines[j].trim() === "") continue;
                  newHtml += "<span class='imk-line'>" + lines[j] + "</span><br>";
              }
              listContainer.innerHTML = newHtml;
              listContainer.dataset.formatted = "true";
          }

          var rootSublist = target.querySelector(".dr5-sublist") || listContainer.parentElement;
          if (rootSublist) {
            rootSublist.addEventListener("click", function(e){
              var a = e.target.closest && e.target.closest("a.imk-cue[data-seek]");
              if(!a) return;
              e.preventDefault();
              var ts = a.getAttribute("data-seek");
              if(!ts) return;
              var p = ts.trim().split(":").map(function(x){return parseInt(x,10)||0;});
              var sec = (p.length===2) ? p[0]*60+p[1] : (p.length===3 ? p[0]*3600+p[1]*60+p[2] : null);
              if(sec==null) return;
              try{ video.currentTime = sec; if(video.paused) video.play(); }catch(_){}
            });
          }
      }

      // ★ dr52.php と全く同じ「常時監視」のロジック
      // これにより、Safariが読み込み遅延を起こしても確実にONになります
      video.addEventListener("timeupdate", function(){
        var hasList = (listContainer !== null);
        var desiredMode = (isSpecialMode() || !hasList) ? "showing" : "hidden";
        
        try {
            if(video.textTracks && video.textTracks.length > 0){
                for(var i=0; i<video.textTracks.length; i++){
                    if((video.textTracks[i].kind === 'subtitles' || video.textTracks[i].kind === 'captions') && video.textTracks[i].mode !== desiredMode) {
                        video.textTracks[i].mode = desiredMode;
                    }
                }
            }
        } catch(e){}

        // リストがない場合はこれ以降のハイライト処理をスキップ
        if (!hasList) return;

        var cues = listContainer.querySelectorAll("a.imk-cue");
        if(cues.length === 0) return;
        var cur = video.currentTime;
        var activeA = null;

        for(var i=0; i<cues.length; i++){
            var ts = cues[i].getAttribute("data-seek");
            if(!ts) continue;
            var p = ts.trim().split(":").map(function(x){return parseInt(x,10)||0;});
            var t = (p.length===2) ? p[0]*60+p[1] : (p.length===3 ? p[0]*3600+p[1]*60+p[2] : null);
            if(t !== null && cur >= t - 0.5){ activeA = cues[i]; } 
            else if(t > cur){ break; }
        }

        if(activeA){
            var activeLine = activeA.closest(".imk-line");
            if(!activeLine) activeLine = activeA;

            if(activeLine.classList.contains("active-hl")) return;

            var allLines = listContainer.querySelectorAll(".imk-line");
            for(var k=0; k<allLines.length; k++) {
                allLines[k].classList.remove("active-hl");
                allLines[k].removeAttribute("style"); 
            }
            var allLinks = listContainer.querySelectorAll("a");
            for(var m=0; m<allLinks.length; m++) {
                allLinks[m].classList.remove("active-hl");
                allLinks[m].removeAttribute("style"); 
            }

            activeLine.classList.add("active-hl");
            activeLine.style.setProperty("background-color", "#ffff00", "important");
            activeLine.style.setProperty("color", "red", "important");
            activeLine.style.setProperty("font-weight", "normal", "important");
            
            var newLinks = activeLine.querySelectorAll("a");
            for(var n=0; n<newLinks.length; n++) {
                newLinks[n].style.setProperty("color", "red", "important");
                newLinks[n].style.setProperty("text-decoration", "none", "important");
            }

            if(listContainer.offsetParent !== null){
                var containerRect = listContainer.getBoundingClientRect();
                var activeRect = activeLine.getBoundingClientRect();
                var targetScroll = listContainer.scrollTop + (activeRect.top - containerRect.top) - (listContainer.clientHeight / 2) + (activeLine.clientHeight / 2);
                listContainer.scrollTo({ top: targetScroll, behavior: "smooth" });
            }
        }
      });

      return true;
    }

    var checks = 0;
    var checkTimer = setInterval(function(){
      protectVideo();
      var success = initSubtitles();
      checks++;
      if (success || checks > 20) { 
        clearInterval(checkTimer);
      }
    }, 500); 

    if (!window.scStopAndGo) {
      window.scStopAndGo = function(event, link){
        try{
          var mediaEls = document.querySelectorAll('video, audio');
          mediaEls.forEach(function(m){
            try{
              if (!m.paused) m.pause();
              if (document.pictureInPictureElement === m && document.exitPictureInPicture) {
                document.exitPictureInPicture().catch(function(){});
              }
            }catch(e){}
          });
        }finally{
          event.preventDefault();
          setTimeout(function(){
            if (link.target === '_blank') {
              window.open(link.href, '_blank');
            } else {
              window.location.href = link.href;
            }
          }, 50);
        }
        return false;
      };
    }
  })();
  </script>
</div>



<p class="wp-block-paragraph">考え方としては、自分にとってのほぼ全ての分野を網羅するNotebookを一つ持って、なんでも相談できる相手に育てたいというところです。</p>



<a rel="noopener" target="_blank" href="https://docs.google.com/presentation/d/1rndjesC2orBsrCojLktlcN6TaUzQQNBXgu-F7E0OzmY/edit?usp=sharing" 
>
<img decoding="async" src="https://docs.google.com/presentation/d/1rndjesC2orBsrCojLktlcN6TaUzQQNBXgu-F7E0OzmY/export/svg"
></a>



<div class="wp-block-cocoon-blocks-toggle-box-1 toggle-wrap toggle-box block-box not-nested-style cocoon-block-toggle"><input id="toggle-checkbox-202608191104280" class="toggle-checkbox" type="checkbox"/><label class="toggle-button" for="toggle-checkbox-202608191104280"><strong><span class="marker">コードと要約</span><span class="marker">(クリック)</span></strong></label><div class="toggle-content">
<figure class="wp-block-flexible-table-block-table"><table class="has-fixed-layout"><tbody><tr><td>要約：このシステムは、ローカルの指定フォルダにあるテキストファイルをGoogleドキュメントへ自動同期するものです。主要なコンポーネントは以下の通りです。 1. `sync_note_sum.py`: ローカルフォルダ内の`.txt`ファイルを更新日時順に取得し、複数のGoogleドキュメントに目次付きで分割して同期（上書き）します。Google Docs APIを利用し、認証情報を管理しながら実行します。 2. `watcher_note_sum.py`: `sync_note_sum.py`と連携し、監視対象フォルダ内の`.txt`ファイルの変更をリアルタイムで検知します。変更後、5秒のクールダウン期間を経て`sync_note_sum.py`を自動実行し、Googleドキュメントの同期をトリガーします。これにより、ファイル更新時に自動で同期が行われます。 3. `com.XXXXXX.notesum-updater.plist`: macOSのLaunchAgents設定ファイルで、ユーザーログイン時に`watcher_note_sum.py`を自動的にバックグラウンドで起動・常駐させます。これにより、システムは常に稼働し、ローカルのメモをGoogleドキュメントに最新の状態に保つ自動化された環境を提供します。</td></tr><tr><td><a href="https://imakat.com/script_list/?pubtxt=%E5%82%99%E5%BF%98%E9%8C%B2%E3%81%AEnotebookLM%E3%81%BB%E3%81%8B%E5%85%A8%E8%87%AA%E5%8B%95%E5%90%8C%E6%9C%9F%E3%83%91%E3%82%A4%E3%83%97%E3%83%A9%E3%82%A4%E3%83%B3_pub.txt" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=zrU95MiY.png" alt="" style="width:120px; height:auto;"></a></td></tr><tr><td>公開スクリプトに関するお問い合わせは以下へ↓</td></tr><tr><td><a rel="noopener" target="_blank" href="https://notebooklm.google.com/notebook/aa64cc3d-0160-4433-b934-ecc5ab902287"><img decoding="async" src="https://imakat.com/rd.php?id=NgFHFYXH.png" alt="" style="width:120px; height:auto;"></a></td></tr></tbody></table></figure>
</div></div>



<h2 class="wp-block-heading"><strong>１　現状の課題：AIの知識ソース管理の壁</strong> </h2>



<p class="wp-block-paragraph">NotebookやGeminiなどのAIエージェントに自分専用の知識を持たせる際、直面するのが以下の壁です。</p>



<ul class="wp-block-list">
<li><strong>フォルダごとの登録ができない：</strong> Googleドライブのフォルダを丸ごと指定して読み込ませることができません。<br></li>



<li><strong>ソース数の上限：</strong> メモを1件1件テキストファイル等で追加していくと、すぐに上限（現状はNotebookで無料版は50個など）に達し、ナレッジベースが「ゴミ屋敷化」してしまいます。<br></li>



<li><strong>「メモに保存」機能の弱点：</strong> AI側の標準機能でメモを保存すると、AIの回答だけが残り、「自分がどのような前提条件や問題意識で質問したか」という文脈が欠落しがちです。<br></li>
</ul>



<h2 class="wp-block-heading"><strong>２　解決策：ローカルの「note_sum」フォルダを起点とした全自動・製本パイプライン</strong> </h2>



<p class="wp-block-paragraph">これらの課題を解決するため、私は「手元のMacでtxtを保存するだけで、裏でシステムが自動的に複数のtxtを束ねてGoogleドキュメント（Gdoc）化し、AIへ同期する」仕組みを構築しました。</p>



<h3 class="wp-block-heading"><strong>① 作成とトリアージ（選別）はローカルで</strong></h3>



<p class="wp-block-paragraph">AIとの対話で「これは残したい！」と思った瞬間、自作の『<a href="https://imakat.com/2025/10/12/27668/" target="_blank">Gemini/ChatGPT連携_要約保存Mac用スクリプト</a>』を使って、まずは「要約＋本文」のテキストファイルとして一時保存します(pre_note_sum)。 そして、後で時間があるときに「本当にAIの知識に組み込む価値があるか」を判断（トリアージ）し、有益なものだけをMac内の指定フォルダ（note_sum）へポンと放り込みます。</p>



<p class="wp-block-paragraph">特に有効な使い方は、<strong>トラブル対策の作業記録(備忘録)</strong>としての活用です。 トラブル発生から解決、そして記録保存までの流れは以下の通りです。</p>



<ol class="wp-block-list">
<li><strong>Notebookで状況説明</strong> 2026年7月現在、チャット欄にスクリーンショットを直接貼れないため、少し手間ですが「ソース」欄にスクショを、状況を端的に伝える最初の１枚として、貼り付けてから状況を説明します。</li>



<li><strong>Gemini / AI Studio(あるいはChatGPT Claudeなど)でコード修正</strong> エラー対策の多くはコード修正になるため、より適したGeminiやGoogle AI Studio、あるいは他のAIにチャットを移行し、作業を完結させます。</li>



<li><strong>テキストファイルへの記録と保存</strong> 複数のAIを渡り歩いた一連のやり取りをtxtファイルにコピペし、要約を追記して「note_sum」フォルダに保存します。その後で、最初の状況説明スクショをソースから削除します。</li>
</ol>



<p class="wp-block-paragraph">この一連の作業記録は（最終的にソースとして）Notebookに集約されることになり、<strong>あちこちのAIで散らばった全プロセスが1箇所に保存され</strong>、非常に安心です。</p>



<h3 class="wp-block-heading"><strong>② 常駐システムによる「自動製本」</strong></h3>



<p class="wp-block-paragraph">note_sum フォルダにテキストファイルが入ると、バックグラウンドで待機している監視スクリプト（watcher_note_sum.py）が自動検知します。 システムは、フォルダ内のテキストファイルを更新順に並べ替え、目次を自動生成し、1冊につき当面は30個のメモを収納した分厚い「備忘録まとめ」へと束ねていきます。</p>



<h3 class="wp-block-heading"><strong>③ 「聖域」の固定ID・Gdocを上書き更新</strong> </h3>



<p class="wp-block-paragraph">システムは、新しくファイルを作るのではなく、Googleドライブの絶対に動かさない場所（cannot_moved/gdoc_imakat/）にあらかじめ用意しておいた当面10個の「空のGdoc」の中身だけを、APIを使って丸ごと最新の状態に書き換えます。</p>



<h2 class="wp-block-heading"><strong>３　このエコシステムを導入するメリット</strong></h2>



<ul class="wp-block-list">
<li><strong>操作の手間がゼロ：</strong> ファイルのID自体は変わらないため、Notebookの自動同期機能が働き、私が何もしなくても勝手にAIの頭脳が最新の備忘録にアップデートされ続けます。<br></li>



<li><strong>Notebookのソース数の限界を突破できる感：</strong>2026年7月現在、Notebookの最大ソース数は300個となっています。しかしメモやgdocをそのままソースに追加すると、そのままソース数を消費してしまいます。しかし今回の方法を使えば、一つのgdocの中に現在設定で30個のtxtファイルが入ります。単純の計算で、内容的には300&#215;30個=9000個のソースが追加できるようになります。<br></li>



<li><strong>いつでも思考の続きからスタート：</strong> 過去の深い考察やトラブル解決の文脈がAIに共有されているため、久しぶりにそのテーマを扱う際も、前提条件を説明する手間が省けます。</li>



<li><strong>txtファイルはどこでも再利用できる：</strong>上の続きですが、ローカルのnote_sumフォルダにあるtxtファイルは、そのまま、Gemini Gemほか生成AIにコピペでき万能です。<br></li>



<li><strong>ゴミ屋敷化の防止と断捨離の連動：</strong> ローカルの note_sum フォルダから不要になったtxtファイルを削除すれば、システムが即座にGdoc内を丸ごと書き直すため、AIの知識からも跡形もなく消え去ります。「ローカルのフォルダ整理＝AIの脳内の整理」が直結しています。<br></li>
</ul>



<h2 class="wp-block-heading"><strong>おわりに</strong></h2>



<p class="wp-block-paragraph">システムが自動で行うべき「面倒な処理（Gdoc化と同期）」は裏側のPythonとLaunchAgentに完璧に任せ、人間は最も重要な「トリアージ（選別・移動）」だけを行う。これこそが、人とAIの完璧な役割分担だと感じています。 当面最大300件(=30txtファイルx10gdocファイル)の備忘録を全自動で受け入れるこの「特大の本棚」に、これからも様々な有益な情報を追加して、AIの考察レベルを限界まで引き上げていこうと思います。</p>



<p class="wp-block-paragraph">以上</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">29358</post-id>	</item>
		<item>
		<title>【Notebook(旧LM)活用】第２弾、コード生成のエコシステムを作ってみた</title>
		<link>https://imakat.com/2026/06/18/29269/</link>
		
		<dc:creator><![CDATA[imakat]]></dc:creator>
		<pubDate>Thu, 18 Jun 2026 12:39:33 +0000</pubDate>
				<category><![CDATA[WordPress]]></category>
		<category><![CDATA[デジタル]]></category>
		<category><![CDATA[ものづくり]]></category>
		<category><![CDATA[Gem]]></category>
		<category><![CDATA[NotebookLM]]></category>
		<category><![CDATA[Google AI Studio]]></category>
		<guid isPermaLink="false">https://imakat.com/?p=29269</guid>

					<description><![CDATA[先日、生成AI（Gem）を活用したコード生成システム（投稿ID-28530）を紹介しましたが、世界中で生成AIへの需要が集中しているせいか、現在の環境にはいくつか大きな悩ましい問題が出てきました 。 現状の混沌とした状況 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">先日、生成AI（Gem）を活用したコード生成システム（投稿<a href="https://imakat.com/2026/02/16/28530/" target="_blank">ID-28530</a>）を紹介しましたが、世界中で生成AIへの需要が集中しているせいか、現在の環境にはいくつか大きな悩ましい問題が出てきました 。</p>



<p class="wp-block-paragraph">現状の混沌とした状況で、私がAntigravityのようなAIに丸投げして自動処理に委ねるのは時期尚早 。そこで今回は、仕組みを大幅に改善した「第２弾：コード作成のエコシステム」を構築しましたのでご紹介します 。</p>



<p class="wp-block-paragraph">私は現在、Google AI Proを利用中です。</p>



<figure class="wp-block-image size-large is-resized"><a href="https://imakat.com/rd.php?id=eySC95du.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=eySC95du.png" alt="" style="width:311px;height:auto"/></a></figure>



<h2 class="wp-block-heading"><strong>現状の課題：コード生成の「利用制限」と「中抜け」</strong></h2>



<p class="wp-block-paragraph">現在、AIによるコード生成において、主に次の2つの問題に直面しています。</p>



<ol class="wp-block-list">
<li>利用制限の壁：アクセス集中により、すぐに利用制限を超えてしまう 。</li>



<li>長文コードの欠落（中抜け）：特に長文のコードを生成しようとすると、全文記述をお願いしても、途中が省略されたり欠落したまま出力されるエラーが頻発する 。</li>
</ol>



<p class="wp-block-paragraph">部分ごとに完成させる方法もありますが、最終的には全体を通して動作させる必要があるため、やはり「修正済みの全文」をそのまま出力してほしいのが本音です 。</p>



<h2 class="wp-block-heading"><strong>解決策：Notebook × Gem × AI Studioの連携エコシステム</strong></h2>



<p class="wp-block-paragraph">これらの課題を解決するため、それぞれのAIの強みを活かした連携フローを組みました。</p>



<a rel="noopener" target="_blank" href="https://docs.google.com/presentation/d/1j-mEqNO_4LKjnv9UHr1PnJuzlumQ_y8q8i1DxO2zkko/edit?usp=sharing" 
>
<img decoding="async" src="https://docs.google.com/presentation/d/1j-mEqNO_4LKjnv9UHr1PnJuzlumQ_y8q8i1DxO2zkko/export/svg"
></a>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">【新規開発】<br>&nbsp; ▼<br>1. Notebookに相談（現状分析 ＆ 指示書の作成）<br>&nbsp; ▼<br>2. コードの長さに応じて分岐<br>&nbsp; ├─ 通常（600行未満） ⇒ Gemに依頼（生のコードを使用）<br>&nbsp; └─ 長文（600行以上） ⇒ Google AI Studioに依頼（秘匿化コードを使用）<br></p>



<p class="wp-block-paragraph">【エラー発生】<br>エラー発生の場合は、大抵は、スクショでエラーの状況を説明したいのですが、Notebookでは画像を読めません。そこで、コード生成Gemを使って解析して〜コード生成Gemに、そのままGemで処理するか、AI Studioにリレーするか、Notebookに鳥瞰的に分析させるか〜を判断させます。</p>



<h3 class="wp-block-heading"><strong>① Notebook：仕様の自動同期と「指示書」の作成</strong></h3>



<p class="wp-block-paragraph">最近のアップデートにより、Notebookのソースに登録したGoogleドキュメント、スプレッドシート、スライドが自動同期されるようになりました 。これにより、現在動作中の最新コードを常にNotebookに理解・分析させることができます 。</p>



<p class="wp-block-paragraph">&#x1f4a1; ポイント ：Notebookはまだコーディング能力や長文出力が非力なため、「Gemに処理を引き継ぐための指示書（プロンプト）」を出力させる役割に集中させます 。</p>



<h3 class="wp-block-heading"><strong>② Gem（通常コード）：テスト実行から検証まで</strong></h3>



<p class="wp-block-paragraph">Notebookが作った指示書を、使い慣れたGemにコピペします 。Gemには現在動作中のコード（Googleドキュメント）が同期されているため、指示書を渡すだけでスムーズにコーディング、テスト実行、検証まで完結できます 。大半の小〜中規模なコードは、このGemの環境で問題なく処理できます 。</p>



<p class="wp-block-paragraph">&#x1f4a1; ポイント ：Gemには、Notebookを知識ソースとして付けることができます。さらにそのNotebookに付いているソースをGemが理解できる仕組みにはなっていますが、どうも情報が遠いのか薄くなりコードの欠落が生じやすいです(26年6月)。従って現状、Gemの知識ソースとしてコード(gdoc)を同期させる方が、はるかに精度が高いです。</p>



<h3 class="wp-block-heading"><strong>③ Google AI Studio（長文コード：600行以上）：中抜け対策の切り札</strong></h3>



<p class="wp-block-paragraph">マイライブラリの中核となるGAS（Google Apps Script）や一部のPHPなど、600行を超える長文コードで「中抜け」を防ぎたいときだけ、オプションとしてGoogle AI Studioを利用します 。</p>



<ul class="wp-block-list">
<li>セキュリティ対策（秘匿化）：AI Studioは基本無料で強力ですが、データが外部へ漏洩するリスクがあります 。そのため、外部公開用のフォルダ（scripts_pub）内にある、重要な情報（APIキーやパスワード等）を隠した「秘匿化コード（_pub.txt）」をコピペして作業を行います 。</li>
</ul>



<p class="wp-block-paragraph">&#x1f4a1; ポイント ：一応、600行を境界として、GemとAI Studioを選別する考えではありますが、作業途中で行数が境を上下することはあり得ますので、実際はほとんどは管理者である私が判断することになります。実際は、長文コードであっても、修正箇所と修正方法が容易である場合は、Notebookの返答あるいはGemとのやり取りで解決することになります。AI Studioを使うのは、大掛かりで困難になった時だけになると思います。</p>



<h3 class="wp-block-heading"><strong>④ </strong>GoogleAIProからAIPlusにダウングレードした時に、境界行数を変更する</h3>



<p class="wp-block-paragraph">コーディング作業はいつも高度な能力が必要なわけではなく、安定期や軽微な修正、小規模な新規開発なら能力を下げても問題は起きません。AIProは月2900円、AIPlusは月725円とびっくりするほどの料金の差があります。ですから、考え方としては、高度なコーディングを行いたい時には、AIProにアップグレードして、安定期はAIPlusにダウングレードするという柔軟な運用を行いたいです。そのAIPlusのダウングレードに合わせて、上記の境界行数をたとえば<strong>600行から300行に変更する</strong>、といった対応を行えば良いわけです。AIPlusにしたときに、ストレージが400GBに制限される点は、私の場合、別途、DAS棚田システムにて、リミット設定をしてコントロールします。</p>



<h2 class="wp-block-heading"><strong>このエコシステムを導入するメリット</strong></h2>



<ul class="wp-block-list">
<li>エラー対応の迅速化：最新のコードが自動同期されているため、前提条件をいちいち説明する手間が省けます 。</li>



<li>コードの品質安定：長文コードをAI Studioに任せることで、イライラする「コードの中抜け」を回避できます 。</li>



<li>安全性の確保：無料の強力なAIを使いつつも、秘匿化コードを挟むことでセキュリティリスクを最小限に抑えられます 。</li>
</ul>



<h2 class="wp-block-heading"><strong>今後の展望：もっとシンプルなエコシステムへ向けて</strong></h2>



<p class="wp-block-paragraph">現時点（2026年6月）では、私が考えるベストな組み合わせですが、もし将来的にNotebookが長文コードの出力を得意とするようになれば、全ての作業が1つで完結します 。</p>



<p class="wp-block-paragraph">テクノロジーの進化スピードを考えると、1年後くらいにはNotebookやGemのさらなる改善によって、もっとシンプルなエコシステムへ移行していることを期待しています。</p>



<h3 class="wp-block-heading"><strong>終わりに：私のAIとの付き合い方</strong></h3>



<p class="wp-block-paragraph">私がデジタル分野に触れているのは、趣味としての楽しみと認知症防止のためです 。そのため、生成AIの利用も以下のように2つに分けて、無理のないペースで楽しんでいます 。</p>



<ol class="wp-block-list">
<li>専門的な利用：このimakatブログやシステムの維持・改善（Google Gemini中心）</li>



<li>日常的な利用：生活サポート（Apple製品中心なので、Siri AIにゆったり親しむ方向）</li>
</ol>



<p class="wp-block-paragraph">あまりお金をかけず、最新技術の恩恵を賢く受けながら、これからもマイペースに開発を楽しんでいこうと思います 。</p>



<p class="wp-block-paragraph">以上</p>



<figure class="wp-block-image size-large is-resized"><a href="https://imakat.com/rd.php?id=j6Uyit2K.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=j6Uyit2K.png" alt="" style="aspect-ratio:0.9162586106730849;width:276px;height:auto"/></a></figure>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">29269</post-id>	</item>
		<item>
		<title>Adobeキャラは無料で使えるか？！</title>
		<link>https://imakat.com/2026/06/04/29138/</link>
		
		<dc:creator><![CDATA[imakat]]></dc:creator>
		<pubDate>Thu, 04 Jun 2026 07:56:04 +0000</pubDate>
				<category><![CDATA[WordPress]]></category>
		<category><![CDATA[デジタル]]></category>
		<category><![CDATA[ものづくり]]></category>
		<category><![CDATA[パペット]]></category>
		<category><![CDATA[Adobe character animator]]></category>
		<guid isPermaLink="false">https://imakat.com/?p=29138</guid>

					<description><![CDATA[最近、自作パペットを作ってきましたが、考え方は「大手依存からの脱却」です。素晴らしい製品を生み出してきたAdobeには感謝の気持ちは大いに持ちながらも、年齢相応というのがあるじゃないですか。高齢者になったら、やっぱり節約 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">最近、自作パペットを作ってきましたが、考え方は「大手依存からの脱却」です。素晴らしい製品を生み出してきたAdobeには感謝の気持ちは大いに持ちながらも、年齢相応というのがあるじゃないですか。高齢者になったら、やっぱり節約、断捨離、やれる範囲に縮めていく。そういうもんです。</p>



<p class="wp-block-paragraph">それで、さあいよいよ、Adobeキャラとおさらばしようとして、ちょっと触ってみて、あれ〜〜意外。まだ、使えるじゃん。と気づいたのです。</p>



<p class="wp-block-paragraph">Adobeのソフトは、どうしても「毎月の課金が必要」というイメージがありませんか？</p>



<p class="wp-block-paragraph">でも実は、<strong>無料の会員状態でも「Adobe Character Animator」でキャラクターを動かすことができるのですね。</strong></p>



<p class="wp-block-paragraph">今回は、私が実際に検証してみた様子を動画とともにお届けします。</p>



<h2 class="wp-block-heading"><strong>&#x1f3ac; まずは検証動画をご覧ください！</strong></h2>


<div class="sc-dynamic-embed">
  <style>
  /* リンクの見た目を整える */
  .sc-dynamic-embed .sc-link-container { 
      display: flex; 
      margin-bottom: 10px; 
      flex-wrap: wrap; 
      align-items: center; 
  }
  .sc-dynamic-embed .sc-link { margin-bottom: 0; }
  .sc-dynamic-embed .sc-link a {
    font-size: 15px; 
    font-weight: normal;
    text-decoration: underline;
    color: #0073aa;
  }
  .sc-dynamic-embed .sc-link a:hover { text-decoration: none; color: #000; }
  
  @media (max-width: 500px) {
    .sc-dynamic-embed .sc-link a { font-size: 13px; }
  }

  .sc-dynamic-embed .imk-line {
      display: inline-block;
      width: 100%;
      border-radius: 2px;
      transition: background-color 0.1s;
  }

  .sc-dynamic-embed #subtitleOverlay,
  .sc-dynamic-embed #scSubtitleOverlay,
  .sc-dynamic-embed .overlay-cue,
  .sc-dynamic-embed .band {
      display: none !important;
      opacity: 0 !important;
      visibility: hidden !important;
      pointer-events: none !important;
  }
  </style>

  <div class="sc-link-container">
    <p class="sc-link">
      <a href="https://imakat.com/ds62/?drid=82" target="_blank"
         onclick="return scStopAndGo(event, this);">
        👉低画質・枠外字幕はこちら
      </a>
    </p>
  </div>

  <style>
            :root{ --dr5emd-max: 1920px; }
            #subtitleOverlay, #scSubtitleOverlay, .overlay-cue, .band { display: none !important; opacity: 0 !important; }
            .imk-line { display: inline-block; width: 100%; border-radius: 2px; transition: background-color 0.1s; }
            .dr5emd-sublist details > p{ height:200px; overflow:auto; background-color:#EDF7FF; padding:2px 6px; margin:0; box-shadow:3px 3px 4px black; position: relative; }
            .dr5emd-sublist details > summary{ padding:2px 6px; width:100%; background-color:#ddd; border:none; box-shadow:3px 3px 4px black; cursor:pointer; list-style:none; }
            
                .video-wrap{position:relative;width:100%;margin:0 auto}
                #myVideo{width:100%;height:auto;min-height:200px;display:block;background:#000;}</style><div class="dr5emd-container"><div class="video-wrap"><video id="myVideo" controls poster="https://imakat.com/rd.php?id=yFH9qr8E.png" playsinline preload="none" style="width:100%;height:auto;">  <source src="https://imakat.com/rd.php?id=04Cq75XY.mp4" type="video/mp4">  <track src="https://imakat.com/rd.php?id=Vt8jJZxf.vtt" label="日本語" srclang="ja" kind="subtitles"></video></div><div class="dr5emd-sublist"><details translate="yes"><summary translate="yes">字幕一覧(クリック)</summary> <p translate="yes">
(<a href="#" class="imk-cue" data-seek="0:00" translate="no">00:00:00</a>) 〜Adobeキャラは無料で使えるか〜<br>
(<a href="#" class="imk-cue" data-seek="0:05" translate="no">00:00:5.</a>) 普通に使えてますね。実際私は今、どういう状態かと言うと、<br>
(<a href="#" class="imk-cue" data-seek="0:10" translate="no">00:00:10</a>) これ、Adobeのですよ。この無料会員という状態ですね。<br>
(<a href="#" class="imk-cue" data-seek="0:20" translate="no">00:00:20</a>) プランを表示…何のこっちゃい。まあ別に何も今、入ってる状態ではないんですよね。<br>
(<a href="#" class="imk-cue" data-seek="0:30" translate="no">00:00:30</a>) そっか。この下の方にですね、このCharacter Animatorがいくつか置いてありますけど、<br>
(<a href="#" class="imk-cue" data-seek="0:45" translate="no">00:00:45</a>) これがね、今開いてるのはこれなんですけども、使えてるんですよね、これね。<br>
(<a href="#" class="imk-cue" data-seek="0:57" translate="no">00:00:57</a>) どういうこっちゃって感じだよね、これ。そのまま、確かもう解約してるんで、<br>
(<a href="#" class="imk-cue" data-seek="1:08" translate="no">00:01:08</a>) 私はこれ、アカウントってのを見た時に、無料会員って格好になってると思うんですよ今。<br>
(<a href="#" class="imk-cue" data-seek="1:19" translate="no">00:01:19</a>) これアカウント管理って見てみようかな。今は私は…あ、だってAdobe Express、<br>
(<a href="#" class="imk-cue" data-seek="1:34" translate="no">00:01:34</a>) 購入になってるから入ってないでしょ今。プランとお支払い、プラン…<br>
(<a href="#" class="imk-cue" data-seek="1:45" translate="no">00:01:45</a>) 有料プランがありません。これね、有料プランがありませんっていう状態なんですよ。<br>
(<a href="#" class="imk-cue" data-seek="1:54" translate="no">00:01:54</a>) なんとなく動作がおかしいというか。で、もう一回、この状態にして、OKにして、<br>
(<a href="#" class="imk-cue" data-seek="2:16" translate="no">00:02:16</a>) フィルターを、クロマキーを入れて、そうだね、それで閉じると。で、この状態にして、<br>
(<a href="#" class="imk-cue" data-seek="2:33" translate="no">00:02:33</a>) ちょっとこれを引いて、角を取っていく。角を取っていく。<br>
(<a href="#" class="imk-cue" data-seek="2:58" translate="no">00:02:58</a>) これでいいのかな？これでいいか。これでいいなら、そうするとだよ、<br>
(<a href="#" class="imk-cue" data-seek="3:13" translate="no">00:03:13</a>) これで今、普通に録音録画できてるわけだから。これ今ね、有料プランがありませんということで、<br>
(<a href="#" class="imk-cue" data-seek="3:24" translate="no">00:03:24</a>) 有料プランがない状態、無料会員になってるわけですね。それでも、このAdobeパペットがですね、<br>
(<a href="#" class="imk-cue" data-seek="3:36" translate="no">00:03:36</a>) 使えてるわけですよ。まあ何かと、Dr.AppleSmithを表示して、ちゃんと一応敬意を表してるっていう格好になるわけですね。<br>
(<a href="#" class="imk-cue" data-seek="3:42" translate="no">00:03:42</a>) じゃあまだ使えるということですかね、無料状態で。会員でありさえすれば。<br>
(<a href="#" class="imk-cue" data-seek="3:50" translate="no">00:03:50</a>) ただいつどこでパタッと使えなくなるか分かりません。<br>
(<a href="#" class="imk-cue" data-seek="3:54" translate="no">00:03:54</a>) あるいはウォーターマークが入るかは分かりません。以上です。<br>
</p> </details>
<style>
details { font: 16px "Open Sans", Calibri, sans-serif; width: 100%; }
details > summary { padding: 2px 6px; width: 100%; background-color: #ddd; border: none; box-shadow: 3px 3px 4px black; cursor: pointer; list-style: none; }
details > p { font: 14px "Open Sans", Calibri, sans-serif; height:150px; overflow: scroll; background-color: #EDF7FF; padding: 2px 6px; margin: 0; box-shadow: 3px 3px 4px black; }
</style>
</div></div>            <script>
            document.addEventListener("DOMContentLoaded", function(){
                var video = document.getElementById("myVideo");
                if(!video) return;

                            });
            </script>
            

  <script>
  (function(){
    var me = document.currentScript;
    var wrapper = me ? me.closest('.sc-dynamic-embed') : null;

    function protectVideo() {
      var target = wrapper ? wrapper : document;
      var mediaEls = target.querySelectorAll('video, audio');
      mediaEls.forEach(function(v){
        if(v.dataset.protected === 'true') return;
        v.dataset.protected = 'true';
        v.setAttribute('controlsList', 'nodownload');
        v.oncontextmenu = function() { return false; };
        v.addEventListener('contextmenu', function(e){ e.preventDefault(); return false; }, false);
      });
    }

    function initSubtitles() {
      var target = wrapper ? wrapper : document;
      var video = target.querySelector('video, audio');
      var listContainer = target.querySelector('details > p');
      
      if (!video) return false; 
      
      if (video.dataset.subInit === 'true') return true; 
      video.dataset.subInit = 'true';

      var oldOverlay = target.querySelector('#subtitleOverlay') || target.querySelector('#scSubtitleOverlay');
      if (oldOverlay) {
          oldOverlay.style.setProperty('display', 'none', 'important');
          oldOverlay.innerHTML = ''; 
      }

      function isSpecialMode() {
        var isFs = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement);
        var isPiP = !!(document.pictureInPictureElement && document.pictureInPictureElement === video) || (video.webkitPresentationMode === "picture-in-picture");
        var isIOSFs = !!video.webkitDisplayingFullscreen; 
        return isFs || isPiP || isIOSFs;
      }

      // 初期状態のセット（リストがなければ最初から showing に）
      try {
        if(video.textTracks && video.textTracks.length > 0){
          for(var i=0; i<video.textTracks.length; i++){
             if(video.textTracks[i].kind === 'subtitles' || video.textTracks[i].kind === 'captions'){
                 video.textTracks[i].mode = (!listContainer) ? "showing" : "hidden";
             }
          }
        }
      } catch(e){}

      // 字幕一覧が存在する場合のみ、リストのフォーマット処理を行う
      if (listContainer) {
          var detailsEl = target.querySelector("details");
          if (detailsEl) {
              detailsEl.open = true; 
              var summaryEl = detailsEl.querySelector("summary");
              if (summaryEl) summaryEl.textContent = "字幕(シーン)はここをクリック";
          }

          if (!listContainer.dataset.formatted) {
              var html = listContainer.innerHTML;
              var lines = html.split(/<br\s*\/?>/i);
              var newHtml = "";
              for(var j=0; j<lines.length; j++) {
                  if(lines[j].trim() === "") continue;
                  newHtml += "<span class='imk-line'>" + lines[j] + "</span><br>";
              }
              listContainer.innerHTML = newHtml;
              listContainer.dataset.formatted = "true";
          }

          var rootSublist = target.querySelector(".dr5-sublist") || listContainer.parentElement;
          if (rootSublist) {
            rootSublist.addEventListener("click", function(e){
              var a = e.target.closest && e.target.closest("a.imk-cue[data-seek]");
              if(!a) return;
              e.preventDefault();
              var ts = a.getAttribute("data-seek");
              if(!ts) return;
              var p = ts.trim().split(":").map(function(x){return parseInt(x,10)||0;});
              var sec = (p.length===2) ? p[0]*60+p[1] : (p.length===3 ? p[0]*3600+p[1]*60+p[2] : null);
              if(sec==null) return;
              try{ video.currentTime = sec; if(video.paused) video.play(); }catch(_){}
            });
          }
      }

      // ★ dr52.php と全く同じ「常時監視」のロジック
      // これにより、Safariが読み込み遅延を起こしても確実にONになります
      video.addEventListener("timeupdate", function(){
        var hasList = (listContainer !== null);
        var desiredMode = (isSpecialMode() || !hasList) ? "showing" : "hidden";
        
        try {
            if(video.textTracks && video.textTracks.length > 0){
                for(var i=0; i<video.textTracks.length; i++){
                    if((video.textTracks[i].kind === 'subtitles' || video.textTracks[i].kind === 'captions') && video.textTracks[i].mode !== desiredMode) {
                        video.textTracks[i].mode = desiredMode;
                    }
                }
            }
        } catch(e){}

        // リストがない場合はこれ以降のハイライト処理をスキップ
        if (!hasList) return;

        var cues = listContainer.querySelectorAll("a.imk-cue");
        if(cues.length === 0) return;
        var cur = video.currentTime;
        var activeA = null;

        for(var i=0; i<cues.length; i++){
            var ts = cues[i].getAttribute("data-seek");
            if(!ts) continue;
            var p = ts.trim().split(":").map(function(x){return parseInt(x,10)||0;});
            var t = (p.length===2) ? p[0]*60+p[1] : (p.length===3 ? p[0]*3600+p[1]*60+p[2] : null);
            if(t !== null && cur >= t - 0.5){ activeA = cues[i]; } 
            else if(t > cur){ break; }
        }

        if(activeA){
            var activeLine = activeA.closest(".imk-line");
            if(!activeLine) activeLine = activeA;

            if(activeLine.classList.contains("active-hl")) return;

            var allLines = listContainer.querySelectorAll(".imk-line");
            for(var k=0; k<allLines.length; k++) {
                allLines[k].classList.remove("active-hl");
                allLines[k].removeAttribute("style"); 
            }
            var allLinks = listContainer.querySelectorAll("a");
            for(var m=0; m<allLinks.length; m++) {
                allLinks[m].classList.remove("active-hl");
                allLinks[m].removeAttribute("style"); 
            }

            activeLine.classList.add("active-hl");
            activeLine.style.setProperty("background-color", "#ffff00", "important");
            activeLine.style.setProperty("color", "red", "important");
            activeLine.style.setProperty("font-weight", "normal", "important");
            
            var newLinks = activeLine.querySelectorAll("a");
            for(var n=0; n<newLinks.length; n++) {
                newLinks[n].style.setProperty("color", "red", "important");
                newLinks[n].style.setProperty("text-decoration", "none", "important");
            }

            if(listContainer.offsetParent !== null){
                var containerRect = listContainer.getBoundingClientRect();
                var activeRect = activeLine.getBoundingClientRect();
                var targetScroll = listContainer.scrollTop + (activeRect.top - containerRect.top) - (listContainer.clientHeight / 2) + (activeLine.clientHeight / 2);
                listContainer.scrollTo({ top: targetScroll, behavior: "smooth" });
            }
        }
      });

      return true;
    }

    var checks = 0;
    var checkTimer = setInterval(function(){
      protectVideo();
      var success = initSubtitles();
      checks++;
      if (success || checks > 20) { 
        clearInterval(checkTimer);
      }
    }, 500); 

    if (!window.scStopAndGo) {
      window.scStopAndGo = function(event, link){
        try{
          var mediaEls = document.querySelectorAll('video, audio');
          mediaEls.forEach(function(m){
            try{
              if (!m.paused) m.pause();
              if (document.pictureInPictureElement === m && document.exitPictureInPicture) {
                document.exitPictureInPicture().catch(function(){});
              }
            }catch(e){}
          });
        }finally{
          event.preventDefault();
          setTimeout(function(){
            if (link.target === '_blank') {
              window.open(link.href, '_blank');
            } else {
              window.location.href = link.href;
            }
          }, 50);
        }
        return false;
      };
    }
  })();
  </script>
</div>



<h2 class="wp-block-heading"><strong>&#x1f4a1; 検証：有料プランなしでも本当に動くの？</strong></h2>



<p class="wp-block-paragraph">現在、私のAdobeアカウントは<strong>有料プランを解約済み</strong>です。</p>



<p class="wp-block-paragraph">アカウント管理画面を確認しても、はっきりと「有料プランがありません」と表示されています。</p>



<p class="wp-block-paragraph">「じゃあ、ソフトは開けないよね？」と思いきや……</p>



<p class="wp-block-paragraph">Creative Cloudのアプリ一覧から「Character Animator」を開いてみると、なんと<strong>普通に起動できてしまいました！</strong></p>



<h2 class="wp-block-heading"><strong>&#x1f6e0;&#xfe0f; 無料状態でどこまでできる？</strong></h2>



<p class="wp-block-paragraph">実際にソフトを触ってみたところ、ただ起動するだけではなく、以下のような操作も問題なく行えました。</p>



<ul class="wp-block-list">
<li><strong>パペットの操作:</strong> 今回は「Dr. AppleSmith」というキャラクター（パペット）を使用。ちゃんと動きます！</li>



<li><strong>録音・録画:</strong> 普通に録音・録画機能が使えます。</li>



<li><strong>クロマキー処理:</strong> フィルターからクロマキーを入れて、グリーンバックを透過する設定も可能。四隅の角を取って調整する作業もできました。</li>
</ul>



<p class="wp-block-paragraph">動画内で私がやっているように、無料会員であっても、基本的なアニメーション制作の環境は整っているようです。</p>



<h2 class="wp-block-heading"><strong>&#x1f9d0; なぜ無料で使えるの？</strong></h2>



<p class="wp-block-paragraph">実は、画面の右上をよく見ると「スターター」という文字が表示されています。</p>



<p class="wp-block-paragraph">Adobe Character Animatorには、誰でも無料で基本的な機能を使える「スターターモード」が用意されているんです。（※Adobeアカウントの無料登録自体は必要です）</p>



<p class="wp-block-paragraph">これなら、「ちょっとキャラを動かしてみたい」「VTuberみたいなことをお試しでやってみたい」という人でも気軽に始められますね！スターターモードといっても、Adobeはセンスがいいから、どれも使い物になりますよ。</p>



<figure class="wp-block-image size-large"><a href="https://imakat.com/rd.php?id=A3KC6bUz.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=A3KC6bUz.png" alt=""/></a></figure>



<h2 class="wp-block-heading"><strong>&#x26a0;&#xfe0f; 使う上での注意点</strong></h2>



<p class="wp-block-paragraph">現時点では無料で快適に使えていますが、いくつか気をつけておきたい点もあります。</p>



<ol class="wp-block-list">
<li><strong>いつ仕様が変わるか分からない:</strong> Adobe側のアップデートや仕様変更により、突然使えなくなる機能が出てくる可能性はゼロではありません。</li>



<li><strong>ウォーターマーク（透かし）の可能性:</strong> 今後の書き出し時に、無料版特有のロゴやウォーターマークが入るようになる可能性も考えられます。</li>
</ol>



<h2 class="wp-block-heading"><strong>&#x1f4dd; まとめ</strong></h2>



<p class="wp-block-paragraph"><strong>結論：無料のAdobe会員状態でも、Character Animator（Starterモード）は使えます！</strong></p>



<p class="wp-block-paragraph">いつまでこの状態で使えるかは分かりませんが、気になっている方は無料で使える今のうちにぜひ試してみてください！<br>とはいうものの、私は、自作パペットに舵を切っていきます。</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">29138</post-id>	</item>
		<item>
		<title>【脱Vimeo】Bunny CDNへ移行しました 〜一ヶ所に依存しない究極の自前配信システム〜</title>
		<link>https://imakat.com/2026/06/01/29030/</link>
		
		<dc:creator><![CDATA[imakat]]></dc:creator>
		<pubDate>Sun, 31 May 2026 20:50:48 +0000</pubDate>
				<category><![CDATA[WordPress]]></category>
		<category><![CDATA[デジタル]]></category>
		<category><![CDATA[ものづくり]]></category>
		<category><![CDATA[vimeo]]></category>
		<category><![CDATA[bunny]]></category>
		<category><![CDATA[cloudflare]]></category>
		<category><![CDATA[自動トランスコード]]></category>
		<guid isPermaLink="false">https://imakat.com/?p=29030</guid>

					<description><![CDATA[上の動画は、Bunny CDNを使っています。プレーヤーはWordPress備え付けをカスタマイズしています。 はじめに 今回は、少しマニアックな動画配信の裏側の話題です。 長年愛用してきたVimeoですが、3月頃に「P [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"></p>


<div class="sc-dynamic-embed">
  <style>
  /* リンクの見た目を整える */
  .sc-dynamic-embed .sc-link-container { 
      display: flex; 
      margin-bottom: 10px; 
      flex-wrap: wrap; 
      align-items: center; 
  }
  .sc-dynamic-embed .sc-link { margin-bottom: 0; }
  .sc-dynamic-embed .sc-link a {
    font-size: 15px; 
    font-weight: normal;
    text-decoration: underline;
    color: #0073aa;
  }
  .sc-dynamic-embed .sc-link a:hover { text-decoration: none; color: #000; }
  
  @media (max-width: 500px) {
    .sc-dynamic-embed .sc-link a { font-size: 13px; }
  }

  .sc-dynamic-embed .imk-line {
      display: inline-block;
      width: 100%;
      border-radius: 2px;
      transition: background-color 0.1s;
  }

  .sc-dynamic-embed #subtitleOverlay,
  .sc-dynamic-embed #scSubtitleOverlay,
  .sc-dynamic-embed .overlay-cue,
  .sc-dynamic-embed .band {
      display: none !important;
      opacity: 0 !important;
      visibility: hidden !important;
      pointer-events: none !important;
  }
  </style>

  <div class="sc-link-container">
    <p class="sc-link">
      <a href="https://imakat.com/ds62/?drid=83" target="_blank"
         onclick="return scStopAndGo(event, this);">
        👉低画質・枠外字幕はこちら
      </a>
    </p>
  </div>

  <style>
            :root{ --dr5emd-max: 1920px; }
            #subtitleOverlay, #scSubtitleOverlay, .overlay-cue, .band { display: none !important; opacity: 0 !important; }
            .imk-line { display: inline-block; width: 100%; border-radius: 2px; transition: background-color 0.1s; }
            .dr5emd-sublist details > p{ height:200px; overflow:auto; background-color:#EDF7FF; padding:2px 6px; margin:0; box-shadow:3px 3px 4px black; position: relative; }
            .dr5emd-sublist details > summary{ padding:2px 6px; width:100%; background-color:#ddd; border:none; box-shadow:3px 3px 4px black; cursor:pointer; list-style:none; }
            
                .video-wrap{position:relative;width:100%;margin:0 auto}
                #myVideo{width:100%;height:auto;min-height:200px;display:block;background:#000;}</style><div class="dr5emd-container"><div class="video-wrap"><video id="myVideo" controls poster="https://imakat.com/rd.php?id=kYlNhSTr.png" playsinline preload="none" style="width:100%;height:auto;">  <source src="https://imakat.com/rd.php?id=ngym69ui.mp4" type="video/mp4">  <track src="https://imakat.com/rd.php?id=jh8hMw5r.vtt" label="日本語" srclang="ja" kind="subtitles"></video></div><div class="dr5emd-sublist"><details translate="yes"><summary translate="yes">字幕一覧(クリック)</summary> <p translate="yes">
(<a href="#" class="imk-cue" data-seek="0:00" translate="no">00:00:00</a>) 〜vimeoからbunnyへの乗り換え〜<br>
(<a href="#" class="imk-cue" data-seek="0:05" translate="no">00:00:05</a>) 皆さんこんにちは。本日は私が長年愛用してきたVimeoから離れ、<br>
(<a href="#" class="imk-cue" data-seek="0:11" translate="no">00:00:11</a>) 特定のサービスに依存しない独自の動画配信システムを構築した理由と、その全貌についてお話しします。<br>
(<a href="#" class="imk-cue" data-seek="0:18" translate="no">00:00:18</a>) キーワードは「プラットフォームからの脱却」と、「完全自動のハンドメイド配信」です。<br>
(<a href="#" class="imk-cue" data-seek="0:25" translate="no">00:00:25</a>) 動画配信において、YouTubeやVimeoといった大手プラットフォームは非常に便利です。<br>
(<a href="#" class="imk-cue" data-seek="0:32" translate="no">00:00:32</a>) しかし、突然の規約変更や料金体系の改定に大きく振り回されるリスクを常に抱えています。<br>
(<a href="#" class="imk-cue" data-seek="0:40" translate="no">00:00:40</a>) また、機能面での限界もありました。例えば、Vimeoの専用プレーヤーでは、<br>
(<a href="#" class="imk-cue" data-seek="0:48" translate="no">00:00:48</a>) 字幕一覧から動画の特定のシーンへ連動してジャンプする、といった<br>
(<a href="#" class="imk-cue" data-seek="0:52" translate="no">00:00:52</a>) 私が理想とする自由な視聴体験を作ることができませんでした。<br>
(<a href="#" class="imk-cue" data-seek="0:55" translate="no">00:00:55</a>) プラットフォームの都合に縛られず、自分自身のメディアライブラリをコントロールしたい。<br>
(<a href="#" class="imk-cue" data-seek="1:00" translate="no">00:01:00</a>) これが自前インフラへの乗り換えを決断した最大の理由です。<br>
(<a href="#" class="imk-cue" data-seek="1:04" translate="no">00:01:04</a>) そこで、私が新しい配信インフラとして選んだのがBunny CDNです。<br>
(<a href="#" class="imk-cue" data-seek="1:10" translate="no">00:01:10</a>) このサービスの最大の魅力は、視聴者の環境に合わせて画質を調整する全自動トランスコード機能を持ちながら、<br>
(<a href="#" class="imk-cue" data-seek="1:18" translate="no">00:01:18</a>) ストリーミング配信用の.m3u8ファイルのダイレクトURL、つまり直リンクを解放してくれる点にあります。<br>
(<a href="#" class="imk-cue" data-seek="1:28" translate="no">00:01:28</a>) これにより、大手の専用プレーヤーを強制されることなく、自作のテンプレート、dr52.phpなどを使って、<br>
(<a href="#" class="imk-cue" data-seek="1:34" translate="no">00:01:34</a>) 動画の再生時間に合わせて字幕がハイライトし、クリックでそのシーンへジャンプできる、<br>
(<a href="#" class="imk-cue" data-seek="1:40" translate="no">00:01:40</a>) 完全に自由でインタラクティブな視聴体験を実現できるようになりました。<br>
(<a href="#" class="imk-cue" data-seek="1:48" translate="no">00:01:48</a>) ではなぜ、同じような機能を持つ強力なライバルであるCloudflare Streamではなく、Bunny CDNを選んだのでしょうか。<br>
(<a href="#" class="imk-cue" data-seek="1:58" translate="no">00:01:58</a>) その理由は、圧倒的なコストパフォーマンスの違いにあります。<br>
(<a href="#" class="imk-cue" data-seek="2:03" translate="no">00:02:03</a>) Cloudflareは動画の再生時間に対して課金されますが、Bunny CDNはデータ転送量に対する課金です。<br>
(<a href="#" class="imk-cue" data-seek="2:11" translate="no">00:02:11</a>) つまり、視聴者が途中で動画を止めたり、スマホなどの小さな画面で低画質モードで再生したりした場合、<br>
(<a href="#" class="imk-cue" data-seek="2:19" translate="no">00:02:19</a>) Bunny CDNなら消費データ量が減るため、インフラ費用を劇的に安く抑えることができます。<br>
(<a href="#" class="imk-cue" data-seek="2:25" translate="no">00:02:25</a>) 個人開発者にとって、このコストの安さは右に出るものがありません。<br>
(<a href="#" class="imk-cue" data-seek="2:30" translate="no">00:02:30</a>) そして最後に、運用を支える裏側のシステムです。複雑な作業は一切必要ありません。<br>
(<a href="#" class="imk-cue" data-seek="2:38" translate="no">00:02:38</a>) ユーザーは手元のスマホやPCからAppSheetを開き、青ボタンを押して、動画のステータスを「処理待ち」に変更するだけです。<br>
(<a href="#" class="imk-cue" data-seek="2:46" translate="no">00:02:46</a>) あとはMacのバックグラウンドで待機しているPythonスクリプト、f1_bunny_factory.pyが1分ごとに自動検知し、<br>
(<a href="#" class="imk-cue" data-seek="2:54" translate="no">00:02:54</a>) Bunny CDNへのアップロードを実行します。<br>
(<a href="#" class="imk-cue" data-seek="2:59" translate="no">00:02:59</a>) 新しいストリーミングURLが生成されると自動的にスプレッドシートが書き換えられ、古いデータは自動で削除されます。<br>
(<a href="#" class="imk-cue" data-seek="3:07" translate="no">00:03:07</a>) 人間の判断と裏側の自動化が完璧に連携した、まさに究極のハンドメイド配信システムの完成です。<br>
(<a href="#" class="imk-cue" data-seek="3:16" translate="no">00:03:16</a>) 特定のプラットフォームへの依存をなくし、視聴者には最高の体験を提供し、なおかつインフラ費用を極限まで安く抑える。<br>
(<a href="#" class="imk-cue" data-seek="3:25" translate="no">00:03:25</a>) Vimeoからの乗り換えは、動画の管理から視聴者の体験まで、すべてを自分自身でデザインできる自由を手に入れるための最高の決断でした。<br>
(<a href="#" class="imk-cue" data-seek="3:34" translate="no">00:03:34</a>) 皆さんもぜひ、ご自身のライブラリ構築に挑戦してみてはいかがでしょうか。ご視聴ありがとうございました。<br>
</p> </details>
<style>
details { font: 16px "Open Sans", Calibri, sans-serif; width: 100%; }
details > summary { padding: 2px 6px; width: 100%; background-color: #ddd; border: none; box-shadow: 3px 3px 4px black; cursor: pointer; list-style: none; }
details > p { font: 14px "Open Sans", Calibri, sans-serif; height:150px; overflow: scroll; background-color: #EDF7FF; padding: 2px 6px; margin: 0; box-shadow: 3px 3px 4px black; }
</style>
</div></div>            <script>
            document.addEventListener("DOMContentLoaded", function(){
                var video = document.getElementById("myVideo");
                if(!video) return;

                            });
            </script>
            

  <script>
  (function(){
    var me = document.currentScript;
    var wrapper = me ? me.closest('.sc-dynamic-embed') : null;

    function protectVideo() {
      var target = wrapper ? wrapper : document;
      var mediaEls = target.querySelectorAll('video, audio');
      mediaEls.forEach(function(v){
        if(v.dataset.protected === 'true') return;
        v.dataset.protected = 'true';
        v.setAttribute('controlsList', 'nodownload');
        v.oncontextmenu = function() { return false; };
        v.addEventListener('contextmenu', function(e){ e.preventDefault(); return false; }, false);
      });
    }

    function initSubtitles() {
      var target = wrapper ? wrapper : document;
      var video = target.querySelector('video, audio');
      var listContainer = target.querySelector('details > p');
      
      if (!video) return false; 
      
      if (video.dataset.subInit === 'true') return true; 
      video.dataset.subInit = 'true';

      var oldOverlay = target.querySelector('#subtitleOverlay') || target.querySelector('#scSubtitleOverlay');
      if (oldOverlay) {
          oldOverlay.style.setProperty('display', 'none', 'important');
          oldOverlay.innerHTML = ''; 
      }

      function isSpecialMode() {
        var isFs = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement);
        var isPiP = !!(document.pictureInPictureElement && document.pictureInPictureElement === video) || (video.webkitPresentationMode === "picture-in-picture");
        var isIOSFs = !!video.webkitDisplayingFullscreen; 
        return isFs || isPiP || isIOSFs;
      }

      // 初期状態のセット（リストがなければ最初から showing に）
      try {
        if(video.textTracks && video.textTracks.length > 0){
          for(var i=0; i<video.textTracks.length; i++){
             if(video.textTracks[i].kind === 'subtitles' || video.textTracks[i].kind === 'captions'){
                 video.textTracks[i].mode = (!listContainer) ? "showing" : "hidden";
             }
          }
        }
      } catch(e){}

      // 字幕一覧が存在する場合のみ、リストのフォーマット処理を行う
      if (listContainer) {
          var detailsEl = target.querySelector("details");
          if (detailsEl) {
              detailsEl.open = true; 
              var summaryEl = detailsEl.querySelector("summary");
              if (summaryEl) summaryEl.textContent = "字幕(シーン)はここをクリック";
          }

          if (!listContainer.dataset.formatted) {
              var html = listContainer.innerHTML;
              var lines = html.split(/<br\s*\/?>/i);
              var newHtml = "";
              for(var j=0; j<lines.length; j++) {
                  if(lines[j].trim() === "") continue;
                  newHtml += "<span class='imk-line'>" + lines[j] + "</span><br>";
              }
              listContainer.innerHTML = newHtml;
              listContainer.dataset.formatted = "true";
          }

          var rootSublist = target.querySelector(".dr5-sublist") || listContainer.parentElement;
          if (rootSublist) {
            rootSublist.addEventListener("click", function(e){
              var a = e.target.closest && e.target.closest("a.imk-cue[data-seek]");
              if(!a) return;
              e.preventDefault();
              var ts = a.getAttribute("data-seek");
              if(!ts) return;
              var p = ts.trim().split(":").map(function(x){return parseInt(x,10)||0;});
              var sec = (p.length===2) ? p[0]*60+p[1] : (p.length===3 ? p[0]*3600+p[1]*60+p[2] : null);
              if(sec==null) return;
              try{ video.currentTime = sec; if(video.paused) video.play(); }catch(_){}
            });
          }
      }

      // ★ dr52.php と全く同じ「常時監視」のロジック
      // これにより、Safariが読み込み遅延を起こしても確実にONになります
      video.addEventListener("timeupdate", function(){
        var hasList = (listContainer !== null);
        var desiredMode = (isSpecialMode() || !hasList) ? "showing" : "hidden";
        
        try {
            if(video.textTracks && video.textTracks.length > 0){
                for(var i=0; i<video.textTracks.length; i++){
                    if((video.textTracks[i].kind === 'subtitles' || video.textTracks[i].kind === 'captions') && video.textTracks[i].mode !== desiredMode) {
                        video.textTracks[i].mode = desiredMode;
                    }
                }
            }
        } catch(e){}

        // リストがない場合はこれ以降のハイライト処理をスキップ
        if (!hasList) return;

        var cues = listContainer.querySelectorAll("a.imk-cue");
        if(cues.length === 0) return;
        var cur = video.currentTime;
        var activeA = null;

        for(var i=0; i<cues.length; i++){
            var ts = cues[i].getAttribute("data-seek");
            if(!ts) continue;
            var p = ts.trim().split(":").map(function(x){return parseInt(x,10)||0;});
            var t = (p.length===2) ? p[0]*60+p[1] : (p.length===3 ? p[0]*3600+p[1]*60+p[2] : null);
            if(t !== null && cur >= t - 0.5){ activeA = cues[i]; } 
            else if(t > cur){ break; }
        }

        if(activeA){
            var activeLine = activeA.closest(".imk-line");
            if(!activeLine) activeLine = activeA;

            if(activeLine.classList.contains("active-hl")) return;

            var allLines = listContainer.querySelectorAll(".imk-line");
            for(var k=0; k<allLines.length; k++) {
                allLines[k].classList.remove("active-hl");
                allLines[k].removeAttribute("style"); 
            }
            var allLinks = listContainer.querySelectorAll("a");
            for(var m=0; m<allLinks.length; m++) {
                allLinks[m].classList.remove("active-hl");
                allLinks[m].removeAttribute("style"); 
            }

            activeLine.classList.add("active-hl");
            activeLine.style.setProperty("background-color", "#ffff00", "important");
            activeLine.style.setProperty("color", "red", "important");
            activeLine.style.setProperty("font-weight", "normal", "important");
            
            var newLinks = activeLine.querySelectorAll("a");
            for(var n=0; n<newLinks.length; n++) {
                newLinks[n].style.setProperty("color", "red", "important");
                newLinks[n].style.setProperty("text-decoration", "none", "important");
            }

            if(listContainer.offsetParent !== null){
                var containerRect = listContainer.getBoundingClientRect();
                var activeRect = activeLine.getBoundingClientRect();
                var targetScroll = listContainer.scrollTop + (activeRect.top - containerRect.top) - (listContainer.clientHeight / 2) + (activeLine.clientHeight / 2);
                listContainer.scrollTo({ top: targetScroll, behavior: "smooth" });
            }
        }
      });

      return true;
    }

    var checks = 0;
    var checkTimer = setInterval(function(){
      protectVideo();
      var success = initSubtitles();
      checks++;
      if (success || checks > 20) { 
        clearInterval(checkTimer);
      }
    }, 500); 

    if (!window.scStopAndGo) {
      window.scStopAndGo = function(event, link){
        try{
          var mediaEls = document.querySelectorAll('video, audio');
          mediaEls.forEach(function(m){
            try{
              if (!m.paused) m.pause();
              if (document.pictureInPictureElement === m && document.exitPictureInPicture) {
                document.exitPictureInPicture().catch(function(){});
              }
            }catch(e){}
          });
        }finally{
          event.preventDefault();
          setTimeout(function(){
            if (link.target === '_blank') {
              window.open(link.href, '_blank');
            } else {
              window.location.href = link.href;
            }
          }, 50);
        }
        return false;
      };
    }
  })();
  </script>
</div>



<div class="wp-block-cocoon-blocks-toggle-box-1 toggle-wrap toggle-box block-box not-nested-style cocoon-block-toggle"><input id="toggle-checkbox-202608151739120" class="toggle-checkbox" type="checkbox"/><label class="toggle-button" for="toggle-checkbox-202608151739120"><strong><span class="marker">コードと要約</span><span class="marker">(クリック)</span></strong></label><div class="toggle-content">
<figure class="wp-block-flexible-table-block-table"><table class="has-fixed-layout"><tbody><tr><td>要約：本システムは、AppSheetとPythonスクリプトが連携し、Googleスプレッドシート上のメタデータに基づき動画・音楽ファイルをBunny CDNへ自動アップロード・管理する。 AppSheet側では、動画ファイル（mp4等）で「bn低画質動画単独生成=TRUE」かつ「bnステータス=処理待ち」の条件を満たすと、「Bunnyファイル生成更新」アクションが起動する。 MacのLaunchAgent (`com.XXXXXX.bunny_factory.plist`) により60秒ごとに実行されるPythonスクリプト`f1_bunny_factory.py`は、スプレッドシートを監視。システム設定のマスターSWが「ON」の場合に、「処理待ち」のレコードを検出する。 検出されたファイルは拡張子に応じ処理される。動画ファイルはBunny Streamへアップロードされ、動画IDとストリーミングURL（高画質・低画質）を生成。音楽ファイルはBunny Storageへアップロードされ、エンコードされたファイル名と直接アクセスURLを生成する。 アップロード後、スプレッドシートは新しいURLなどで更新され、「処理待ち」ステータスが「完了」となる。同時に、スプレッドシートに残る古い動画IDやファイル名が存在すれば、Bunny CDNからそれらを削除し、ログに記録する。これにより、Bunny CDN上のコンテンツが自動で最新に保たれる。</td></tr><tr><td><a href="https://imakat.com/script_list/?pubtxt=bunny%E6%9B%B4%E6%96%B0%E3%83%91%E3%82%A4%E3%83%97%E3%83%A9%E3%82%A4%E3%83%B3_pub.txt" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=zrU95MiY.png" alt="" style="width:120px; height:auto;"></a></td></tr><tr><td>公開スクリプトに関するお問い合わせは以下へ↓</td></tr><tr><td><a rel="noopener" target="_blank" href="https://notebooklm.google.com/notebook/aa64cc3d-0160-4433-b934-ecc5ab902287"><img decoding="async" src="https://imakat.com/rd.php?id=NgFHFYXH.png" alt="" style="width:120px; height:auto;"></a></td></tr></tbody></table></figure>
</div></div>



<p class="wp-block-paragraph">上の動画は、<a rel="noopener" target="_blank" href="https://bunny.net">Bunny CDN<span class="fa fa-external-link external-icon anchor-icon"></span></a>を使っています。プレーヤーはWordPress備え付けをカスタマイズしています。</p>



<p class="wp-block-paragraph"></p>



<h3 class="wp-block-heading"><strong>はじめに</strong></h3>



<p class="wp-block-paragraph">今回は、少しマニアックな動画配信の裏側の話題です。 長年愛用してきたVimeoですが、3月頃に「Plusプランが廃止の方向」という記事を目にしました。それに伴い、現在月額750円程度の料金が、1,200円程度に値上がりする方向だそうです。私の動画配信の利用レベルからすると、これは明らかな無駄遣いになってしまいます。 「いよいよVimeoの代替を探す時が来た」と決断し、日頃から頼りにしているGeminiなどのAIと相談しながら、今回の移行作業を進めてきました。</p>



<h3 class="wp-block-heading">１　コスパ最強のCDN選び：Bunny vs Cloudflare</h3>



<p class="wp-block-paragraph">移行先を検討する上で、最終的な候補に残ったのは「Bunny CDN（Bunny Stream）」と「Cloudflare Stream」の2つでした。両者のコストパフォーマンスを比較した結果、私はBunny CDNを選択しました。</p>



<p class="wp-block-paragraph">Bunny CDNの最大の魅力は、月額の最低料金がたったの1ドル（約150円）であることです。 支払いの仕組みは「デポジット制」を採用しており、自動チャージと手動チャージが選べます。感覚としては交通系ICカードのSuicaと同じです。私は手動チャージにしてあります。そして優れている点は、デポジット残高が数日分(概ね数日〜一週間程度)の使用量相当まで減った場合、通知が来る仕組みになっているので何らかのトラブルや暴走を止めることができます（→下記５へ)。 これによって、Vimeoをこのまま放置していれば月額1,200円かかるところを、Bunny CDNなら月額150〜300円程度にまで節約できそうです。小規模な個人運営にとって、このコスト差は圧倒的です。</p>



<h3 class="wp-block-heading">２　料金だけじゃない！「自動トランスコード」と「直リンク」の解放</h3>



<p class="wp-block-paragraph">Bunny CDNを選んだ理由は、単に安いからだけではありません。 Vimeoの大きなメリットであった「視聴者の環境に合わせて画質を調整する自動トランスコード機能」を、Bunny CDNもしっかりと備えています。</p>



<p class="wp-block-paragraph">さらに決定的な違いが、ストリーミング配信用（.m3u8）の「直リンクURL（ダイレクトURL）」がそのまま使える点です。 Vimeoのような大手サービスでは、専用のプレーヤーを通すことが強制され、カスタマイズが困難でした（例えば、字幕一覧から特定のシーンへジャンプするなど）。しかし、直リンクが解放されているBunny CDNなら、自作のテンプレート（dr52.phpなど）を使って、オリジナルの自由な配信の仕組みを完全にコントロールすることができます。</p>



<h3 class="wp-block-heading">３　英語の壁は「AIのサポート」で乗り越える</h3>



<p class="wp-block-paragraph">Bunny CDNを導入する上での唯一の難点は、サービス画面やマニュアルがすべて「英語」であることです。 しかし、今の時代、GeminiやChatGPTといった生成AIにサポートしてもらえば、英語はまったくハンデになりません。設定で分からないことがあればAIに聞き、PythonやAppSheetのシステム構築もAIと二人三脚で進めることができます。この強力なサポートがあれば、高齢者であっても最先端のインフラを構築することは十分に可能です。</p>



<h3 class="wp-block-heading">４　複数の配信所を切り替える「マイライブラリ」の完成</h3>



<p class="wp-block-paragraph">今回の移行以前からですが、私が構築した「マイライブラリ」システムは、状況に応じて複数の配信サーバーを即座に切り替えられるようになっています。</p>



<p class="wp-block-paragraph">それぞれのサーバーには特徴的な役割があります。</p>



<ul class="wp-block-list">
<li><strong>Dropbox(1)</strong>：動画の製造工場からの「産直（ダイレクト配信）」。</li>



<li><strong>Bunny(2)</strong>：今回追加、自動トランスコード付きの配信所。</li>



<li><strong>Xserver(3)</strong>：安全で安定したWordPressの「メインのホスティングサーバー」であり、配信を振り分けるルーティングの中枢。</li>



<li><strong>pCloud(4)</strong>：買い切りプランを活かした「保存倉庫」。</li>
</ul>



<p class="wp-block-paragraph">もし配信にトラブルが起きても、Xserver、pCloud、Dropboxへ速やかに切り替えることができます。 ここに、強力なCDN配信所として「Bunny CDN」が加わった形になります。このような柔軟で自立したエコシステムが完成した今、私がVimeoなどの大手プラットフォームに依存し続けるメリットは殆どなくなりました。</p>



<figure class="wp-block-image size-large"><a href="https://imakat.com/rd.php?id=mWKZ7s1W.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=mWKZ7s1W.png" alt=""/></a></figure>



<h3 class="wp-block-heading">５　逆転の発想〜再生数が多過ぎたら、その動画をYouTubeへ隔離する。</h3>



<p class="wp-block-paragraph">そんなことはないとは思いますが、もし動画に過度に人気や関心が集まり再生数が増大してしまい、Bunnyの料金負担が重いと感じるようになってしまったとします。具体的には月2$(300円)を超えるような場合です。その場合は、その動画を特定して（Bunnyのダッシュボードの左サイドバー→Monitoring→Logs→Report→Video Libraryから自分のライブラリを選択→Top10 URLs by Traffic）、その動画をYouTubeへ隔離するようにしたいと思います、残念ながら広告が入るかも知れませんが。その動画とて、公共の道具であるインターネットを通じて公開してきているわけですので、観たい聴きたいというニーズに対しては、応えていく必要はあると思います。</p>



<figure class="wp-block-image size-large is-resized"><a href="https://imakat.com/rd.php?id=3C6jITcb.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=3C6jITcb.png" alt="" style="aspect-ratio:1.6631418895569838;width:345px;height:auto"/></a></figure>



<p class="wp-block-paragraph">また、BunnyのStreamとして保存したGB数の上限を自分で決めて、それを超えた場合は、古いものについては、Bunnyから除去して、ファイル選択もBunnyを外して、標準の拡張子別の再生に切り替えるようにすることも考えています。</p>



<p class="wp-block-paragraph">このようにマイライブラリを使うことにより、出費をコントロールしながら、自由自在に、動画の配信所の選択ができるわけです。</p>



<h3 class="wp-block-heading"><strong>おわりに：世界情勢が物語る「脱・依存」の重要性</strong></h3>



<p class="wp-block-paragraph">特定のプラットフォームにすべてを預けてしまうと、規約変更や大幅な値上げ、あるいはサービス終了の際に、自分のメディアが人質に取られたような状態になってしまいます。最近の世界情勢が物語っているように、何か一つの場所やサービスに過度に依存しないことは、情報発信においても非常に重要です。</p>



<p class="wp-block-paragraph">Vimeoからの卒業は、まさに自分の資産を自分自身でコントロールする自由を手に入れるための、最良の決断でした。これからも色々工夫して、このブログの、無料かつ非営利の配信を堅持していきます。</p>



<figure class="wp-block-image size-large is-resized"><a href="https://imakat.com/rd.php?id=9nVGc96J.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=9nVGc96J.png" alt="" style="aspect-ratio:1.5367977270644304;width:239px;height:auto"/></a></figure>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">29030</post-id>	</item>
		<item>
		<title>【Mac】Webカメラと声で動く！「自作パペットシステム」を作りました〜無料配布〜</title>
		<link>https://imakat.com/2026/04/18/28865/</link>
		
		<dc:creator><![CDATA[imakat]]></dc:creator>
		<pubDate>Fri, 17 Apr 2026 22:01:50 +0000</pubDate>
				<category><![CDATA[WordPress]]></category>
		<category><![CDATA[デジタル]]></category>
		<category><![CDATA[ものづくり]]></category>
		<category><![CDATA[パペット]]></category>
		<category><![CDATA[アバター]]></category>
		<category><![CDATA[Adobeサブスク]]></category>
		<guid isPermaLink="false">https://imakat.com/?p=28865</guid>

					<description><![CDATA[今回は、Webカメラとマイクを使って、自分の顔の動きや声に合わせて画面上の動作人形（パペット）を動かせる「自作パペットシステム」をご紹介します。「自分専用のオリジナルアバターを、もっと手軽に動かしてみたい！」という思いか [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">今回は、Webカメラとマイクを使って、自分の顔の動きや声に合わせて画面上の動作人形（パペット）を動かせる「自作パペットシステム」をご紹介します。<br>「自分専用のオリジナルアバターを、もっと手軽に動かしてみたい！」という思いからPythonで開発を進めていたシステムですが、<a href="https://imakat.com/2026/03/12/28692/" target="_blank">前回の記事の内容に改良を加え</a>、やっと誰でも簡単に使えるパッケージになりました。<br>この辺で、<strong>Adobeのパペットにバイバイして、Adobeのサブスクをキャンセルすることにします</strong>。</p>



<p class="wp-block-paragraph">今回は<strong>サンプルパペット付きのアプリ（ZIPファイル）を無料公開</strong>します。プログラミングが分からない方でもすぐに使えるように工夫したので、ぜひダウンロードして試してみてください。</p>


<div class="sc-dynamic-embed">
  <style>
  /* リンクの見た目を整える */
  .sc-dynamic-embed .sc-link-container { 
      display: flex; 
      margin-bottom: 10px; 
      flex-wrap: wrap; 
      align-items: center; 
  }
  .sc-dynamic-embed .sc-link { margin-bottom: 0; }
  .sc-dynamic-embed .sc-link a {
    font-size: 15px; 
    font-weight: normal;
    text-decoration: underline;
    color: #0073aa;
  }
  .sc-dynamic-embed .sc-link a:hover { text-decoration: none; color: #000; }
  
  @media (max-width: 500px) {
    .sc-dynamic-embed .sc-link a { font-size: 13px; }
  }

  .sc-dynamic-embed .imk-line {
      display: inline-block;
      width: 100%;
      border-radius: 2px;
      transition: background-color 0.1s;
  }

  .sc-dynamic-embed #subtitleOverlay,
  .sc-dynamic-embed #scSubtitleOverlay,
  .sc-dynamic-embed .overlay-cue,
  .sc-dynamic-embed .band {
      display: none !important;
      opacity: 0 !important;
      visibility: hidden !important;
      pointer-events: none !important;
  }
  </style>

  <div class="sc-link-container">
    <p class="sc-link">
      <a href="https://imakat.com/ds62/?drid=79" target="_blank"
         onclick="return scStopAndGo(event, this);">
        👉低画質・枠外字幕はこちら
      </a>
    </p>
  </div>

  <style>
            :root{ --dr5emd-max: 1920px; }
            #subtitleOverlay, #scSubtitleOverlay, .overlay-cue, .band { display: none !important; opacity: 0 !important; }
            .imk-line { display: inline-block; width: 100%; border-radius: 2px; transition: background-color 0.1s; }
            .dr5emd-sublist details > p{ height:200px; overflow:auto; background-color:#EDF7FF; padding:2px 6px; margin:0; box-shadow:3px 3px 4px black; position: relative; }
            .dr5emd-sublist details > summary{ padding:2px 6px; width:100%; background-color:#ddd; border:none; box-shadow:3px 3px 4px black; cursor:pointer; list-style:none; }
            
                .video-wrap{position:relative;width:100%;margin:0 auto}
                #myVideo{width:100%;height:auto;min-height:200px;display:block;background:#000;}</style><div class="dr5emd-container"><div class="video-wrap"><video id="myVideo" controls poster="https://imakat.com/rd.php?id=CVBqwr5k.png" playsinline preload="none" style="width:100%;height:auto;">  <source src="https://imakat.com/rd.php?id=exd87RDG.mp4" type="video/mp4">  <track src="https://imakat.com/rd.php?id=0zTMlFvp.vtt" label="日本語" srclang="ja" kind="subtitles"></video></div><div class="dr5emd-sublist"><details translate="yes"><summary translate="yes">字幕一覧(クリック)</summary> <p translate="yes">
(<a href="#" class="imk-cue" data-seek="0:00" translate="no">00:00:00</a>) 〜自作パペットサンプル起動〜<br>
(<a href="#" class="imk-cue" data-seek="0:12" translate="no">00:00:12</a>) これがですね、ブログの記事に掲載しますサンプルパペットです。<br>
(<a href="#" class="imk-cue" data-seek="0:24" translate="no">00:00:24</a>) で、これそうですね、キャラを実行。<br>
(<a href="#" class="imk-cue" data-seek="0:29" translate="no">00:00:29</a>) うん、こんな風にですね、いくつもできちゃうんですね、面白いですね。<br>
(<a href="#" class="imk-cue" data-seek="0:34" translate="no">00:00:34</a>) 例えば、ここでimakat1号の方も起動させますとね。<br>
(<a href="#" class="imk-cue" data-seek="0:43" translate="no">00:00:43</a>) そうすると、ほら、これがなんと、<br>
(<a href="#" class="imk-cue" data-seek="0:46" translate="no">00:00:46</a>) キャラクターが、サンプルが2つと、同じものが2つと、<br>
(<a href="#" class="imk-cue" data-seek="0:58" translate="no">00:00:58</a>) もう1つ、imakat1号ですね。このキャラクターも動いていると。<br>
(<a href="#" class="imk-cue" data-seek="1:07" translate="no">00:01:07</a>) これはカメラとマイクを同じものを使っているものですから、3つとも同じ動作になっていますが。<br>
(<a href="#" class="imk-cue" data-seek="1:17" translate="no">00:01:17</a>) これはカメラもマイクも一応、キャラクターごとに設定できるようにしてあるので、<br>
(<a href="#" class="imk-cue" data-seek="1:27" translate="no">00:01:27</a>) やりようによってはですね、違うキャラクターが登場して違う人が操作すると、<br>
(<a href="#" class="imk-cue" data-seek="1:36" translate="no">00:01:36</a>) ということがですね、可能になるようになっています。<br>
(<a href="#" class="imk-cue" data-seek="1:42" translate="no">00:01:42</a>) それでは、この仕組みをどのように導入していくかということについて、<br>
(<a href="#" class="imk-cue" data-seek="1:50" translate="no">00:01:50</a>) ブログの記事をお読みいただきたいと思います。<br>
</p> </details>
<style>
details { font: 16px "Open Sans", Calibri, sans-serif; width: 100%; }
details > summary { padding: 2px 6px; width: 100%; background-color: #ddd; border: none; box-shadow: 3px 3px 4px black; cursor: pointer; list-style: none; }
details > p { font: 14px "Open Sans", Calibri, sans-serif; height:150px; overflow: scroll; background-color: #EDF7FF; padding: 2px 6px; margin: 0; box-shadow: 3px 3px 4px black; }
</style>
</div></div>            <script>
            document.addEventListener("DOMContentLoaded", function(){
                var video = document.getElementById("myVideo");
                if(!video) return;

                            });
            </script>
            

  <script>
  (function(){
    var me = document.currentScript;
    var wrapper = me ? me.closest('.sc-dynamic-embed') : null;

    function protectVideo() {
      var target = wrapper ? wrapper : document;
      var mediaEls = target.querySelectorAll('video, audio');
      mediaEls.forEach(function(v){
        if(v.dataset.protected === 'true') return;
        v.dataset.protected = 'true';
        v.setAttribute('controlsList', 'nodownload');
        v.oncontextmenu = function() { return false; };
        v.addEventListener('contextmenu', function(e){ e.preventDefault(); return false; }, false);
      });
    }

    function initSubtitles() {
      var target = wrapper ? wrapper : document;
      var video = target.querySelector('video, audio');
      var listContainer = target.querySelector('details > p');
      
      if (!video) return false; 
      
      if (video.dataset.subInit === 'true') return true; 
      video.dataset.subInit = 'true';

      var oldOverlay = target.querySelector('#subtitleOverlay') || target.querySelector('#scSubtitleOverlay');
      if (oldOverlay) {
          oldOverlay.style.setProperty('display', 'none', 'important');
          oldOverlay.innerHTML = ''; 
      }

      function isSpecialMode() {
        var isFs = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement);
        var isPiP = !!(document.pictureInPictureElement && document.pictureInPictureElement === video) || (video.webkitPresentationMode === "picture-in-picture");
        var isIOSFs = !!video.webkitDisplayingFullscreen; 
        return isFs || isPiP || isIOSFs;
      }

      // 初期状態のセット（リストがなければ最初から showing に）
      try {
        if(video.textTracks && video.textTracks.length > 0){
          for(var i=0; i<video.textTracks.length; i++){
             if(video.textTracks[i].kind === 'subtitles' || video.textTracks[i].kind === 'captions'){
                 video.textTracks[i].mode = (!listContainer) ? "showing" : "hidden";
             }
          }
        }
      } catch(e){}

      // 字幕一覧が存在する場合のみ、リストのフォーマット処理を行う
      if (listContainer) {
          var detailsEl = target.querySelector("details");
          if (detailsEl) {
              detailsEl.open = true; 
              var summaryEl = detailsEl.querySelector("summary");
              if (summaryEl) summaryEl.textContent = "字幕(シーン)はここをクリック";
          }

          if (!listContainer.dataset.formatted) {
              var html = listContainer.innerHTML;
              var lines = html.split(/<br\s*\/?>/i);
              var newHtml = "";
              for(var j=0; j<lines.length; j++) {
                  if(lines[j].trim() === "") continue;
                  newHtml += "<span class='imk-line'>" + lines[j] + "</span><br>";
              }
              listContainer.innerHTML = newHtml;
              listContainer.dataset.formatted = "true";
          }

          var rootSublist = target.querySelector(".dr5-sublist") || listContainer.parentElement;
          if (rootSublist) {
            rootSublist.addEventListener("click", function(e){
              var a = e.target.closest && e.target.closest("a.imk-cue[data-seek]");
              if(!a) return;
              e.preventDefault();
              var ts = a.getAttribute("data-seek");
              if(!ts) return;
              var p = ts.trim().split(":").map(function(x){return parseInt(x,10)||0;});
              var sec = (p.length===2) ? p[0]*60+p[1] : (p.length===3 ? p[0]*3600+p[1]*60+p[2] : null);
              if(sec==null) return;
              try{ video.currentTime = sec; if(video.paused) video.play(); }catch(_){}
            });
          }
      }

      // ★ dr52.php と全く同じ「常時監視」のロジック
      // これにより、Safariが読み込み遅延を起こしても確実にONになります
      video.addEventListener("timeupdate", function(){
        var hasList = (listContainer !== null);
        var desiredMode = (isSpecialMode() || !hasList) ? "showing" : "hidden";
        
        try {
            if(video.textTracks && video.textTracks.length > 0){
                for(var i=0; i<video.textTracks.length; i++){
                    if((video.textTracks[i].kind === 'subtitles' || video.textTracks[i].kind === 'captions') && video.textTracks[i].mode !== desiredMode) {
                        video.textTracks[i].mode = desiredMode;
                    }
                }
            }
        } catch(e){}

        // リストがない場合はこれ以降のハイライト処理をスキップ
        if (!hasList) return;

        var cues = listContainer.querySelectorAll("a.imk-cue");
        if(cues.length === 0) return;
        var cur = video.currentTime;
        var activeA = null;

        for(var i=0; i<cues.length; i++){
            var ts = cues[i].getAttribute("data-seek");
            if(!ts) continue;
            var p = ts.trim().split(":").map(function(x){return parseInt(x,10)||0;});
            var t = (p.length===2) ? p[0]*60+p[1] : (p.length===3 ? p[0]*3600+p[1]*60+p[2] : null);
            if(t !== null && cur >= t - 0.5){ activeA = cues[i]; } 
            else if(t > cur){ break; }
        }

        if(activeA){
            var activeLine = activeA.closest(".imk-line");
            if(!activeLine) activeLine = activeA;

            if(activeLine.classList.contains("active-hl")) return;

            var allLines = listContainer.querySelectorAll(".imk-line");
            for(var k=0; k<allLines.length; k++) {
                allLines[k].classList.remove("active-hl");
                allLines[k].removeAttribute("style"); 
            }
            var allLinks = listContainer.querySelectorAll("a");
            for(var m=0; m<allLinks.length; m++) {
                allLinks[m].classList.remove("active-hl");
                allLinks[m].removeAttribute("style"); 
            }

            activeLine.classList.add("active-hl");
            activeLine.style.setProperty("background-color", "#ffff00", "important");
            activeLine.style.setProperty("color", "red", "important");
            activeLine.style.setProperty("font-weight", "normal", "important");
            
            var newLinks = activeLine.querySelectorAll("a");
            for(var n=0; n<newLinks.length; n++) {
                newLinks[n].style.setProperty("color", "red", "important");
                newLinks[n].style.setProperty("text-decoration", "none", "important");
            }

            if(listContainer.offsetParent !== null){
                var containerRect = listContainer.getBoundingClientRect();
                var activeRect = activeLine.getBoundingClientRect();
                var targetScroll = listContainer.scrollTop + (activeRect.top - containerRect.top) - (listContainer.clientHeight / 2) + (activeLine.clientHeight / 2);
                listContainer.scrollTo({ top: targetScroll, behavior: "smooth" });
            }
        }
      });

      return true;
    }

    var checks = 0;
    var checkTimer = setInterval(function(){
      protectVideo();
      var success = initSubtitles();
      checks++;
      if (success || checks > 20) { 
        clearInterval(checkTimer);
      }
    }, 500); 

    if (!window.scStopAndGo) {
      window.scStopAndGo = function(event, link){
        try{
          var mediaEls = document.querySelectorAll('video, audio');
          mediaEls.forEach(function(m){
            try{
              if (!m.paused) m.pause();
              if (document.pictureInPictureElement === m && document.exitPictureInPicture) {
                document.exitPictureInPicture().catch(function(){});
              }
            }catch(e){}
          });
        }finally{
          event.preventDefault();
          setTimeout(function(){
            if (link.target === '_blank') {
              window.open(link.href, '_blank');
            } else {
              window.location.href = link.href;
            }
          }, 50);
        }
        return false;
      };
    }
  })();
  </script>
</div>



<h2 class="wp-block-heading"><strong>&#x2728; 自作パペットシステムの3つの特徴</strong></h2>



<h3 class="wp-block-heading"><strong>1. Webカメラとマイクだけでスルスル動く</strong></h3>



<p class="wp-block-paragraph">高価なトラッキング機材は不要です。Macの内蔵カメラ（またはWebカメラ）が自分の顔の傾きを認識し、マイクが声の大きさを拾って、パペットがリアルタイムに連動します。</p>



<p class="wp-block-paragraph">「あいうえお」の口の形にも対応しており、まばたきや視線の移動も自動で行います。</p>



<h3 class="wp-block-heading"><strong>2. 専用の設定画面（UI）でカンタン調整</strong></h3>



<p class="wp-block-paragraph">以前はプログラムのコードを直接書き換える必要がありましたが、<strong>専用の「司令塔」となる設定画面</strong>を用意しました。</p>



<p class="wp-block-paragraph">「マイクの感度」「パペットの大きさ」「顔の傾き具合」などを、数値で直感的に微調整できます。自分好みのセッティングを見つけたら、キャラクターごとに保存しておくことが可能です。</p>



<figure class="wp-block-image size-large is-resized"><a href="https://imakat.com/rd.php?id=VMpI3SLD.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=VMpI3SLD.png" alt="" style="aspect-ratio:0.7289086301642892;width:350px;height:auto"/></a></figure>



<h3 class="wp-block-heading"><strong>3. スペースキーでいつでも正面をリセット！</strong></h3>



<p class="wp-block-paragraph">使っている途中で「ちょっと顔の向きがズレてきたな…」と思ったら、<strong>スペースキーをポンッと押すだけ</strong>。その瞬間の自分の顔の位置を「真正面」として再設定（キャリブレーション）します。</p>



<h2 class="wp-block-heading"><strong>&#x1f4e5; ダウンロードと使い方</strong></h2>



<p class="wp-block-paragraph">以下のリンクから、一式がまとまったZIPファイルをダウンロードしてください。</p>



<figure class="wp-block-flexible-table-block-table"><table class="has-fixed-layout"><tbody><tr><td>&#x1f449;<strong>ダウンロード(Macのみ)(無料・許諾不要)</strong></td></tr><tr><td><a href="https://imakat.com/rd.php?id=HFOB6Eer.zip" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=ShLhgSW9.png" alt="" style="width:120px; height:auto;"></a></td></tr></tbody></table></figure>



<h3 class="wp-block-heading"><strong>使い方の手順（※Mac専用です）</strong></h3>



<p class="wp-block-paragraph">使うためには、お使いのMacに「Python」が入っている必要があります。（入っていない方は<a rel="noopener" target="_blank" href="https://www.python.org/downloads/">公式サイト<span class="fa fa-external-link external-icon anchor-icon"></span></a>からインストールしてください）</p>



<ol class="wp-block-list">
<li>ダウンロードしたZIPを解凍し、フォルダを開きます。</li>



<li>初回のみ、フォルダ内の『はじめにお読みください.txt』の手順に従って、必要なAIパーツ（MediaPipeなど）をインストールします。<strong>（※ターミナルにドラッグ＆ドロップするだけです！）</strong></li>



<li>準備ができたら、**「自作パペット起動.command」**をダブルクリック！</li>



<li>設定画面が開くので、キャラクターに「サンプル」を選んで「起動」ボタンを押せば完了です。</li>
</ol>



<p class="wp-block-paragraph"><em>※初回起動時はMacのセキュリティで弾かれることがあります。その場合はファイルを「右クリック」→「開く」を選択してください。カメラのアクセス、マイクのアクセスの許可を求められたら「許可」を選択してください。</em></p>



<figure class="wp-block-image size-large is-resized"><a href="https://imakat.com/rd.php?id=XPV7FP9Y.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=XPV7FP9Y.png" alt="" style="width:299px;height:auto"/></a></figure>



<figure class="wp-block-image size-large is-resized"><a href="https://imakat.com/rd.php?id=WxKXfHO7.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=WxKXfHO7.png" alt="" style="width:297px;height:auto"/></a></figure>



<h2 class="wp-block-heading"><strong>&#x1f3a8; 応用編：オリジナルのパペットを自作しよう！</strong></h2>



<p class="wp-block-paragraph">今回お配りしたフォルダの中には、run_assets/サンプル という場所に、パペットのデザインの元になった <strong>「サンプル.pxd」</strong> というファイルが入っています。<br></p>



<figure class="wp-block-image size-large"><a href="https://imakat.com/rd.php?id=UqYOUOjm.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=UqYOUOjm.png" alt=""/></a></figure>



<p class="wp-block-paragraph">Macの画像編集ソフト「<strong>Pixelmator Pro</strong>」をお持ちの方は、このファイルを開いて自分の好きな絵に描き換えるだけで、<strong>完全オリジナルの自作パペット</strong>を作ることができます！</p>



<p class="wp-block-paragraph">さらに、Pixelmator Proから画像を書き出してパペットを起動するまでを「ワンクリック」で全自動化する裏技（AppleScriptとAutomatorの連携）も開発しました。私は、サブスク版ではない、以前からある単発アプリを使っています。もしサブスク版で「パペット書き出し起動.app」がうまく動かなくなった場合は、Automatorで作成したAppleScript内の tell application &#8220;Pixelmator Pro&#8221; という1行を、新しいサブスク版の正確なアプリ名に書き換えるだけで解決します。</p>



<p class="wp-block-paragraph">コードや詳しい仕組みに興味がある方は、こちらのページにスクリプトを公開していますので、ぜひ覗いてみてください。</p>



<div class="wp-block-cocoon-blocks-toggle-box-1 toggle-wrap toggle-box block-box not-nested-style cocoon-block-toggle"><input id="toggle-checkbox-202605220456280" class="toggle-checkbox" type="checkbox"/><label class="toggle-button" for="toggle-checkbox-202605220456280"><strong><span class="marker">コードと要約</span><span class="marker">(クリック)</span></strong></label><div class="toggle-content">
<figure class="wp-block-flexible-table-block-table"><table class="has-fixed-layout"><tbody><tr><td>要約：このシステムは、顔認識と音声入力を利用してリアルタイムで動作するデスクトップパペットを実現します。<br><code>run_ui.py</code>は、パペットの各種設定を行うGUIアプリケーションです。キャラクターの新規作成、複製、選択、そしてボリューム閾値、グローバルスケール、顔の傾き制限、目の感度、カメラ/マイクIDといったパラメータ調整機能を提供します。これらの設定はキャラクターごとにJSONファイルとして保存されます。<br><code>run_puppet.py</code>は、UIで選択・設定されたキャラクターとパラメータに基づき、実際のパペット動作を制御するスクリプトです。OpenCVとMediaPipe Face Meshを用いてカメラ映像から顔の傾き、目の動き、口の形を検出し、マイクからの音声ボリュームと連携させてアバターの頭の回転、視線、口の動きをリアルタイムで再現します。キャリブレーションデータもキャラクターごとに保存されます。<br>Automatorスクリプトは、これらのPythonスクリプトの実行を補助します。「自作パペット起動.app」は設定UIを立ち上げ、「パペット書き出し起動.app」はPixelmator Proで開いているドキュメントの各レイヤーをPNG画像として書き出し、キャラクターごとの設定JSONを自動生成した後、該当するパペットを起動する一連の処理を行います。これにより、Pixelmator Proでの画像編集からパペット起動までがスムーズに連携されます。</td></tr><tr><td><a href="https://imakat.com/script_list/?pubtxt=自作パペット_py_pub.txt" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=zrU95MiY.png" alt="" style="width:120px; height:auto;"></a></td></tr><tr><td>公開スクリプトに関するお問い合わせは以下へ↓</td></tr><tr><td><a rel="noopener" target="_blank" href="https://gemini.google.com/gem/1Dp-NLSA5j5OXE3B1Ki2IaBRCvq4CGa6t?usp=sharing"><img decoding="async" src="https://imakat.com/rd.php?id=s7BMZHhB.png" alt="" style="width:120px; height:auto;"></a></td></tr></tbody></table></figure>
</div></div>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph">PixelmatorProによるパーツの変更をパペット起動アプリに即座に反映している動画を以下に掲載します。<br></p>



<p class="wp-block-paragraph"></p>


<div class="sc-dynamic-embed">
  <style>
  /* リンクの見た目を整える */
  .sc-dynamic-embed .sc-link-container { 
      display: flex; 
      margin-bottom: 10px; 
      flex-wrap: wrap; 
      align-items: center; 
  }
  .sc-dynamic-embed .sc-link { margin-bottom: 0; }
  .sc-dynamic-embed .sc-link a {
    font-size: 15px; 
    font-weight: normal;
    text-decoration: underline;
    color: #0073aa;
  }
  .sc-dynamic-embed .sc-link a:hover { text-decoration: none; color: #000; }
  
  @media (max-width: 500px) {
    .sc-dynamic-embed .sc-link a { font-size: 13px; }
  }

  .sc-dynamic-embed .imk-line {
      display: inline-block;
      width: 100%;
      border-radius: 2px;
      transition: background-color 0.1s;
  }

  .sc-dynamic-embed #subtitleOverlay,
  .sc-dynamic-embed #scSubtitleOverlay,
  .sc-dynamic-embed .overlay-cue,
  .sc-dynamic-embed .band {
      display: none !important;
      opacity: 0 !important;
      visibility: hidden !important;
      pointer-events: none !important;
  }
  </style>

  <div class="sc-link-container">
    <p class="sc-link">
      <a href="https://imakat.com/ds62/?drid=78" target="_blank"
         onclick="return scStopAndGo(event, this);">
        👉低画質・枠外字幕はこちら
      </a>
    </p>
  </div>

  <style>
            :root{ --dr5emd-max: 1920px; }
            #subtitleOverlay, #scSubtitleOverlay, .overlay-cue, .band { display: none !important; opacity: 0 !important; }
            .imk-line { display: inline-block; width: 100%; border-radius: 2px; transition: background-color 0.1s; }
            .dr5emd-sublist details > p{ height:200px; overflow:auto; background-color:#EDF7FF; padding:2px 6px; margin:0; box-shadow:3px 3px 4px black; position: relative; }
            .dr5emd-sublist details > summary{ padding:2px 6px; width:100%; background-color:#ddd; border:none; box-shadow:3px 3px 4px black; cursor:pointer; list-style:none; }
            
                .video-wrap{position:relative;width:100%;margin:0 auto}
                #myVideo{width:100%;height:auto;min-height:200px;display:block;background:#000;}</style><div class="dr5emd-container"><div class="video-wrap"><video id="myVideo" controls poster="https://imakat.com/rd.php?id=fuNs32gP.png" playsinline preload="none" style="width:100%;height:auto;">  <source src="https://imakat.com/rd.php?id=uCHEt9eh.mp4" type="video/mp4">  <track src="https://imakat.com/rd.php?id=yf1V8irn.vtt" label="日本語" srclang="ja" kind="subtitles"></video></div><div class="dr5emd-sublist"><details translate="yes"><summary translate="yes">字幕一覧(クリック)</summary> <p translate="yes">
(<a href="#" class="imk-cue" data-seek="0:00" translate="no">00:00:00</a>) 〜パペット生成パイプライン〜PixelmatorProからPython連携〜<br>
(<a href="#" class="imk-cue" data-seek="0:11" translate="no">00:00:11</a>) このbodyの方がですね、選ばれて書き込まれるということになります。<br>
(<a href="#" class="imk-cue" data-seek="0:18" translate="no">00:00:18</a>) で、紺のbodyの方はちょっとこう、脇にですね、よけておきます。<br>
(<a href="#" class="imk-cue" data-seek="0:25" translate="no">00:00:25</a>) さあこれで、今これファイルをですね、保存します。<br>
(<a href="#" class="imk-cue" data-seek="0:30" translate="no">00:00:30</a>) そして、「パペットの書き出し起動」を起動させます。<br>
(<a href="#" class="imk-cue" data-seek="0:36" translate="no">00:00:36</a>) そうするとダダダダダッと動いていってですね、<br>
(<a href="#" class="imk-cue" data-seek="0:42" translate="no">00:00:42</a>) なんと、あっという間に、紺の服装からベージュの服装に、お着替えができました。<br>
(<a href="#" class="imk-cue" data-seek="0:56" translate="no">00:00:56</a>) さあそれではまた今度ですね、また紺の服装に戻ってみたいと思います。<br>
(<a href="#" class="imk-cue" data-seek="1:02" translate="no">00:01:02</a>) はい今度これをずらして、紺の服装に着替えて、<br>
(<a href="#" class="imk-cue" data-seek="1:11" translate="no">00:01:11</a>) で、bodyをこっちはベージュにします。<br>
(<a href="#" class="imk-cue" data-seek="1:22" translate="no">00:01:22</a>) で、紺の方をですね、これ紺のを取り外します。bodyだけにします。<br>
(<a href="#" class="imk-cue" data-seek="1:32" translate="no">00:01:32</a>) そうすると今度入れ替わるはずですよね。はい、それではプログラムを起動させます。<br>
(<a href="#" class="imk-cue" data-seek="1:45" translate="no">00:01:45</a>) はい、どうでしょうか。入れ替わりましたね。これで入れ替わったわけです。<br>
(<a href="#" class="imk-cue" data-seek="1:57" translate="no">00:01:57</a>) こんなように簡単にですね、入れ替わる事ができます。<br>
(<a href="#" class="imk-cue" data-seek="2:02" translate="no">00:02:02</a>) やり方としては、Pixelmator Proにファイルを作って、<br>
(<a href="#" class="imk-cue" data-seek="2:07" translate="no">00:02:07</a>) そのファイル上のパーツを入れ替えるということができます。<br>
(<a href="#" class="imk-cue" data-seek="2:11" translate="no">00:02:11</a>) そしてこのアプリを起動させることによって、サッと入れ替えができるという、<br>
(<a href="#" class="imk-cue" data-seek="2:19" translate="no">00:02:19</a>) 優れた仕組みができております。以上です。<br>
</p> </details>
<style>
details { font: 16px "Open Sans", Calibri, sans-serif; width: 100%; }
details > summary { padding: 2px 6px; width: 100%; background-color: #ddd; border: none; box-shadow: 3px 3px 4px black; cursor: pointer; list-style: none; }
details > p { font: 14px "Open Sans", Calibri, sans-serif; height:150px; overflow: scroll; background-color: #EDF7FF; padding: 2px 6px; margin: 0; box-shadow: 3px 3px 4px black; }
</style>
</div></div>            <script>
            document.addEventListener("DOMContentLoaded", function(){
                var video = document.getElementById("myVideo");
                if(!video) return;

                            });
            </script>
            

  <script>
  (function(){
    var me = document.currentScript;
    var wrapper = me ? me.closest('.sc-dynamic-embed') : null;

    function protectVideo() {
      var target = wrapper ? wrapper : document;
      var mediaEls = target.querySelectorAll('video, audio');
      mediaEls.forEach(function(v){
        if(v.dataset.protected === 'true') return;
        v.dataset.protected = 'true';
        v.setAttribute('controlsList', 'nodownload');
        v.oncontextmenu = function() { return false; };
        v.addEventListener('contextmenu', function(e){ e.preventDefault(); return false; }, false);
      });
    }

    function initSubtitles() {
      var target = wrapper ? wrapper : document;
      var video = target.querySelector('video, audio');
      var listContainer = target.querySelector('details > p');
      
      if (!video) return false; 
      
      if (video.dataset.subInit === 'true') return true; 
      video.dataset.subInit = 'true';

      var oldOverlay = target.querySelector('#subtitleOverlay') || target.querySelector('#scSubtitleOverlay');
      if (oldOverlay) {
          oldOverlay.style.setProperty('display', 'none', 'important');
          oldOverlay.innerHTML = ''; 
      }

      function isSpecialMode() {
        var isFs = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement);
        var isPiP = !!(document.pictureInPictureElement && document.pictureInPictureElement === video) || (video.webkitPresentationMode === "picture-in-picture");
        var isIOSFs = !!video.webkitDisplayingFullscreen; 
        return isFs || isPiP || isIOSFs;
      }

      // 初期状態のセット（リストがなければ最初から showing に）
      try {
        if(video.textTracks && video.textTracks.length > 0){
          for(var i=0; i<video.textTracks.length; i++){
             if(video.textTracks[i].kind === 'subtitles' || video.textTracks[i].kind === 'captions'){
                 video.textTracks[i].mode = (!listContainer) ? "showing" : "hidden";
             }
          }
        }
      } catch(e){}

      // 字幕一覧が存在する場合のみ、リストのフォーマット処理を行う
      if (listContainer) {
          var detailsEl = target.querySelector("details");
          if (detailsEl) {
              detailsEl.open = true; 
              var summaryEl = detailsEl.querySelector("summary");
              if (summaryEl) summaryEl.textContent = "字幕(シーン)はここをクリック";
          }

          if (!listContainer.dataset.formatted) {
              var html = listContainer.innerHTML;
              var lines = html.split(/<br\s*\/?>/i);
              var newHtml = "";
              for(var j=0; j<lines.length; j++) {
                  if(lines[j].trim() === "") continue;
                  newHtml += "<span class='imk-line'>" + lines[j] + "</span><br>";
              }
              listContainer.innerHTML = newHtml;
              listContainer.dataset.formatted = "true";
          }

          var rootSublist = target.querySelector(".dr5-sublist") || listContainer.parentElement;
          if (rootSublist) {
            rootSublist.addEventListener("click", function(e){
              var a = e.target.closest && e.target.closest("a.imk-cue[data-seek]");
              if(!a) return;
              e.preventDefault();
              var ts = a.getAttribute("data-seek");
              if(!ts) return;
              var p = ts.trim().split(":").map(function(x){return parseInt(x,10)||0;});
              var sec = (p.length===2) ? p[0]*60+p[1] : (p.length===3 ? p[0]*3600+p[1]*60+p[2] : null);
              if(sec==null) return;
              try{ video.currentTime = sec; if(video.paused) video.play(); }catch(_){}
            });
          }
      }

      // ★ dr52.php と全く同じ「常時監視」のロジック
      // これにより、Safariが読み込み遅延を起こしても確実にONになります
      video.addEventListener("timeupdate", function(){
        var hasList = (listContainer !== null);
        var desiredMode = (isSpecialMode() || !hasList) ? "showing" : "hidden";
        
        try {
            if(video.textTracks && video.textTracks.length > 0){
                for(var i=0; i<video.textTracks.length; i++){
                    if((video.textTracks[i].kind === 'subtitles' || video.textTracks[i].kind === 'captions') && video.textTracks[i].mode !== desiredMode) {
                        video.textTracks[i].mode = desiredMode;
                    }
                }
            }
        } catch(e){}

        // リストがない場合はこれ以降のハイライト処理をスキップ
        if (!hasList) return;

        var cues = listContainer.querySelectorAll("a.imk-cue");
        if(cues.length === 0) return;
        var cur = video.currentTime;
        var activeA = null;

        for(var i=0; i<cues.length; i++){
            var ts = cues[i].getAttribute("data-seek");
            if(!ts) continue;
            var p = ts.trim().split(":").map(function(x){return parseInt(x,10)||0;});
            var t = (p.length===2) ? p[0]*60+p[1] : (p.length===3 ? p[0]*3600+p[1]*60+p[2] : null);
            if(t !== null && cur >= t - 0.5){ activeA = cues[i]; } 
            else if(t > cur){ break; }
        }

        if(activeA){
            var activeLine = activeA.closest(".imk-line");
            if(!activeLine) activeLine = activeA;

            if(activeLine.classList.contains("active-hl")) return;

            var allLines = listContainer.querySelectorAll(".imk-line");
            for(var k=0; k<allLines.length; k++) {
                allLines[k].classList.remove("active-hl");
                allLines[k].removeAttribute("style"); 
            }
            var allLinks = listContainer.querySelectorAll("a");
            for(var m=0; m<allLinks.length; m++) {
                allLinks[m].classList.remove("active-hl");
                allLinks[m].removeAttribute("style"); 
            }

            activeLine.classList.add("active-hl");
            activeLine.style.setProperty("background-color", "#ffff00", "important");
            activeLine.style.setProperty("color", "red", "important");
            activeLine.style.setProperty("font-weight", "normal", "important");
            
            var newLinks = activeLine.querySelectorAll("a");
            for(var n=0; n<newLinks.length; n++) {
                newLinks[n].style.setProperty("color", "red", "important");
                newLinks[n].style.setProperty("text-decoration", "none", "important");
            }

            if(listContainer.offsetParent !== null){
                var containerRect = listContainer.getBoundingClientRect();
                var activeRect = activeLine.getBoundingClientRect();
                var targetScroll = listContainer.scrollTop + (activeRect.top - containerRect.top) - (listContainer.clientHeight / 2) + (activeLine.clientHeight / 2);
                listContainer.scrollTo({ top: targetScroll, behavior: "smooth" });
            }
        }
      });

      return true;
    }

    var checks = 0;
    var checkTimer = setInterval(function(){
      protectVideo();
      var success = initSubtitles();
      checks++;
      if (success || checks > 20) { 
        clearInterval(checkTimer);
      }
    }, 500); 

    if (!window.scStopAndGo) {
      window.scStopAndGo = function(event, link){
        try{
          var mediaEls = document.querySelectorAll('video, audio');
          mediaEls.forEach(function(m){
            try{
              if (!m.paused) m.pause();
              if (document.pictureInPictureElement === m && document.exitPictureInPicture) {
                document.exitPictureInPicture().catch(function(){});
              }
            }catch(e){}
          });
        }finally{
          event.preventDefault();
          setTimeout(function(){
            if (link.target === '_blank') {
              window.open(link.href, '_blank');
            } else {
              window.location.href = link.href;
            }
          }, 50);
        }
        return false;
      };
    }
  })();
  </script>
</div>



<h2 class="wp-block-heading"><strong>&#x1f4a1; OBSでリアルタイム配信に使いたい方へ</strong></h2>



<p class="wp-block-paragraph">このパペットシステムは、OBS Studioを使ってリアルタイム配信などの画面に合成することも可能です。</p>



<p class="wp-block-paragraph">ウィンドウキャプチャでパペットの画面を取り込み、背景（グリーンバック等）を透過させればOKです。</p>



<p class="wp-block-paragraph"><strong>【ワンポイントアドバイス：音ズレの直し方】</strong></p>



<p class="wp-block-paragraph">映像の処理には少し時間がかかるため、OBS上で「声（マイク）」に対して「パペットの口の動き（映像）」がわずかに遅れて見えることがあります。</p>



<p class="wp-block-paragraph">その場合は、OBSの「オーディオの詳細プロパティ」から、<strong>マイクの「同期オフセット」を「200ms〜500ms」ほどプラス</strong>に設定して、声をわざと遅らせて映像に合わせると自然になります。私は今500msに設定しています。</p>



<figure class="wp-block-image size-large"><a href="https://imakat.com/rd.php?id=Avm1FlzY.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=Avm1FlzY.png" alt=""/></a></figure>



<h2 class="wp-block-heading"><strong>おわりに</strong></h2>



<p class="wp-block-paragraph">自分が描いたイラストが、自分の動きに合わせて滑らかに動く体験は、何度やっても感動します。それではご自由に利用ください。</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">28865</post-id>	</item>
		<item>
		<title>デジタル成果物を効率的に管理・配信する2つの自作システム</title>
		<link>https://imakat.com/2026/03/16/28734/</link>
		
		<dc:creator><![CDATA[imakat]]></dc:creator>
		<pubDate>Mon, 16 Mar 2026 05:26:51 +0000</pubDate>
				<category><![CDATA[デジタル]]></category>
		<category><![CDATA[ものづくり]]></category>
		<category><![CDATA[マイライブラリ]]></category>
		<category><![CDATA[棚田式ファイル管理]]></category>
		<category><![CDATA[メタデータ]]></category>
		<guid isPermaLink="false">https://imakat.com/?p=28734</guid>

					<description><![CDATA[今回は、動画や画像といった「デジタル成果物」を効率よく生成し、スムーズに流通（配信・管理）させるための独自の仕組みについてお話しします。 日々増え続ける大容量データをどのように安全かつ柔軟に扱うか、悩んでいる方も多いので [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">今回は、動画や画像といった「デジタル成果物」を効率よく生成し、スムーズに流通（配信・管理）させるための独自の仕組みについてお話しします。</p>



<p class="wp-block-paragraph">日々増え続ける大容量データをどのように安全かつ柔軟に扱うか、悩んでいる方も多いのではないでしょうか？</p>



<p class="wp-block-paragraph">詳しい解説は以下の動画にまとめていますので、まずはご覧ください。</p>


<div class="sc-dynamic-embed">
  <style>
  /* リンクの見た目を整える */
  .sc-dynamic-embed .sc-link-container { 
      display: flex; 
      margin-bottom: 10px; 
      flex-wrap: wrap; 
      align-items: center; 
  }
  .sc-dynamic-embed .sc-link { margin-bottom: 0; }
  .sc-dynamic-embed .sc-link a {
    font-size: 15px; 
    font-weight: normal;
    text-decoration: underline;
    color: #0073aa;
  }
  .sc-dynamic-embed .sc-link a:hover { text-decoration: none; color: #000; }
  
  @media (max-width: 500px) {
    .sc-dynamic-embed .sc-link a { font-size: 13px; }
  }

  .sc-dynamic-embed .imk-line {
      display: inline-block;
      width: 100%;
      border-radius: 2px;
      transition: background-color 0.1s;
  }

  .sc-dynamic-embed #subtitleOverlay,
  .sc-dynamic-embed #scSubtitleOverlay,
  .sc-dynamic-embed .overlay-cue,
  .sc-dynamic-embed .band {
      display: none !important;
      opacity: 0 !important;
      visibility: hidden !important;
      pointer-events: none !important;
  }
  </style>

  <div class="sc-link-container">
    <p class="sc-link">
      <a href="https://imakat.com/ds62/?drid=75" target="_blank"
         onclick="return scStopAndGo(event, this);">
        👉低画質・枠外字幕はこちら
      </a>
    </p>
  </div>

  <style>
            :root{ --dr5emd-max: 1920px; }
            #subtitleOverlay, #scSubtitleOverlay, .overlay-cue, .band { display: none !important; opacity: 0 !important; }
            .imk-line { display: inline-block; width: 100%; border-radius: 2px; transition: background-color 0.1s; }
            .dr5emd-sublist details > p{ height:200px; overflow:auto; background-color:#EDF7FF; padding:2px 6px; margin:0; box-shadow:3px 3px 4px black; position: relative; }
            .dr5emd-sublist details > summary{ padding:2px 6px; width:100%; background-color:#ddd; border:none; box-shadow:3px 3px 4px black; cursor:pointer; list-style:none; }
            
                .video-wrap{position:relative;width:100%;margin:0 auto}
                #myVideo{width:100%;height:auto;min-height:200px;display:block;background:#000;}</style><div class="dr5emd-container"><div class="video-wrap"><video id="myVideo" controls poster="https://imakat.com/rd.php?id=nBmS0llp.png" playsinline preload="none" style="width:100%;height:auto;">  <source src="https://imakat.com/rd.php?id=0D4FHSGD.mp4" type="video/mp4">  <track src="https://imakat.com/rd.php?id=4rjGEGI3.vtt" label="日本語" srclang="ja" kind="subtitles"></video></div><div class="dr5emd-sublist"><details translate="yes"><summary translate="yes">字幕一覧(クリック)</summary> <p translate="yes">
(<a href="#" class="imk-cue" data-seek="0:00" translate="no">00:00:00</a>)  デジタル成果物の生成と流通<br>
(<a href="#" class="imk-cue" data-seek="0:06" translate="no">00:00:06</a>)  それではですね、デジタル成果物の生成と流通ということについて、お話します。<br>
(<a href="#" class="imk-cue" data-seek="0:17" translate="no">00:00:17</a>)  今日はですね、2つのシステムについて説明します。<br>
(<a href="#" class="imk-cue" data-seek="0:23" translate="no">00:00:23</a>)  1つはですね、マイライブラリですね。もう1つは棚田式ファイル管理です。<br>
(<a href="#" class="imk-cue" data-seek="0:32" translate="no">00:00:32</a>)  で、マイライブラリですけれども、これは以前にも一度紹介してますが<br>
(<a href="#" class="imk-cue" data-seek="0:41" translate="no">00:00:41</a>)  Dropboxによる製造工場、この中で作られたメディア配信物、<br>
(<a href="#" class="imk-cue" data-seek="0:50" translate="no">00:00:50</a>)  動画だとか画像、VTT、書類、いわゆるウェブ配信で使う素材ですね。<br>
(<a href="#" class="imk-cue" data-seek="1:00" translate="no">00:01:00</a>)  工場からすれば成果物、配信用の成果物を作って、それを動画・画像配信所に渡して、<br>
(<a href="#" class="imk-cue" data-seek="1:10" translate="no">00:01:10</a>)  リクエストに応じて、それを表示するという形ですが、<br>
(<a href="#" class="imk-cue" data-seek="1:17" translate="no">00:01:17</a>)  その時に動画配信所はですね、いくつも複数用意できるという仕組み、<br>
(<a href="#" class="imk-cue" data-seek="1:25" translate="no">00:01:25</a>)  複数用意した配信所を自由自在に切り替えれるという仕組みですね、それを作ったわけです。<br>
(<a href="#" class="imk-cue" data-seek="1:33" translate="no">00:01:33</a>)  それともう一つの棚田式ファイル管理システムというのはですね、ファイル管理のシステムです。<br>
(<a href="#" class="imk-cue" data-seek="1:41" translate="no">00:01:41</a>)  ただ、通常言われるNASによるファイル管理というのはですね、<br>
(<a href="#" class="imk-cue" data-seek="1:49" translate="no">00:01:49</a>)  同じサイズのハードディスクを2つ用意して置かなきゃいけないと。RAID 1の場合ですね。<br>
(<a href="#" class="imk-cue" data-seek="1:57" translate="no">00:01:57</a>)  しかもそのフォーマットが特殊なフォーマットになってるので、<br>
(<a href="#" class="imk-cue" data-seek="2:05" translate="no">00:02:05</a>)  そのハードディスクだけ引き抜いて、パソコンにガチャンと付けてデータを読むとか、<br>
(<a href="#" class="imk-cue" data-seek="2:14" translate="no">00:02:14</a>)  そういうことができないわけですね。一番いいのは、いつもMacやパソコンに付けている<br>
(<a href="#" class="imk-cue" data-seek="2:22" translate="no">00:02:22</a>)  ハードディスクやSSDと同じように、パコッと抜いて、<br>
(<a href="#" class="imk-cue" data-seek="2:30" translate="no">00:02:30</a>)  他のパソコンやMacに挿してすぐデータが読める状態になっていれば一番いいですよね。<br>
(<a href="#" class="imk-cue" data-seek="2:38" translate="no">00:02:38</a>)  そういう形にしたいということ、つまり、簡単にデータを移行して、<br>
(<a href="#" class="imk-cue" data-seek="2:46" translate="no">00:02:46</a>)  それをそのまま別に転用ができるということですね。そういう使い方がしたいわけですよ。<br>
(<a href="#" class="imk-cue" data-seek="2:54" translate="no">00:02:54</a>)  ハードディスクが高価になっていくというところに対応していく必要もあるし、<br>
(<a href="#" class="imk-cue" data-seek="3:02" translate="no">00:03:02</a>)  使わずに残っているハードディスクを有効活用するということもあるわけですね。<br>
(<a href="#" class="imk-cue" data-seek="3:09" translate="no">00:03:09</a>)  自由にデバイスをファイル管理用に使えるという仕組みを作ったわけです。<br>
(<a href="#" class="imk-cue" data-seek="3:18" translate="no">00:03:18</a>)  それが棚田式ファイル管理システムです。構造的には一番上に作業段、<br>
(<a href="#" class="imk-cue" data-seek="3:26" translate="no">00:03:26</a>)  通常作業するために使う工場のような場所、それから完成したものを保存する保管段、<br>
(<a href="#" class="imk-cue" data-seek="3:34" translate="no">00:03:34</a>)  さらに保管段のうち、長期になったものを長期保管段に移行する仕組みを作りました。<br>
(<a href="#" class="imk-cue" data-seek="3:43" translate="no">00:03:43</a>)  この2つのシステムは両方ともローカルのMac mini上で動いているという優れものでございます。<br>
(<a href="#" class="imk-cue" data-seek="3:55" translate="no">00:03:55</a>)  このマイライブラリと棚田式ファイル管理の共通点なんですけども、これはこういうことなんです。<br>
(<a href="#" class="imk-cue" data-seek="4:11" translate="no">00:04:11</a>)  両方ともメタデータの変更に基づき、実体データを操作するという特徴があります。<br>
(<a href="#" class="imk-cue" data-seek="4:26" translate="no">00:04:26</a>)  棚田システムの方は、メタデータを移動することによるシミュレーションを何回もやって、<br>
(<a href="#" class="imk-cue" data-seek="4:35" translate="no">00:04:35</a>)  その各デバイスの容量、中身をコントロールするということをシミュレーションしていくと。<br>
(<a href="#" class="imk-cue" data-seek="4:45" translate="no">00:04:45</a>)  そのことによって実体データの移動のミステイクを事前に防止してるわけです。<br>
(<a href="#" class="imk-cue" data-seek="4:52" translate="no">00:04:52</a>)  それともう一つのマイライブラリの方ですけれども、これはメタデータ、動画や画像の属性ですね、<br>
(<a href="#" class="imk-cue" data-seek="5:01" translate="no">00:05:01</a>)  それをスプレッドシートの中に登録していきます。で、その属性に応じて、<br>
(<a href="#" class="imk-cue" data-seek="5:09" translate="no">00:05:09</a>)  ファイルが移動したり、再生サーバーを変更したり、そうした操作が行われると。<br>
(<a href="#" class="imk-cue" data-seek="5:16" translate="no">00:05:16</a>)  いう仕組みですね。<br>
(<a href="#" class="imk-cue" data-seek="5:24" translate="no">00:05:24</a>)  メタデータの変更に基づき実体データを操作するというところが、この2つのシステムの共通点です。<br>
</p> </details>
<style>
details { font: 16px "Open Sans", Calibri, sans-serif; width: 100%; }
details > summary { padding: 2px 6px; width: 100%; background-color: #ddd; border: none; box-shadow: 3px 3px 4px black; cursor: pointer; list-style: none; }
details > p { font: 14px "Open Sans", Calibri, sans-serif; height:150px; overflow: scroll; background-color: #EDF7FF; padding: 2px 6px; margin: 0; box-shadow: 3px 3px 4px black; }
</style>
</div></div>            <script>
            document.addEventListener("DOMContentLoaded", function(){
                var video = document.getElementById("myVideo");
                if(!video) return;

                            });
            </script>
            

  <script>
  (function(){
    var me = document.currentScript;
    var wrapper = me ? me.closest('.sc-dynamic-embed') : null;

    function protectVideo() {
      var target = wrapper ? wrapper : document;
      var mediaEls = target.querySelectorAll('video, audio');
      mediaEls.forEach(function(v){
        if(v.dataset.protected === 'true') return;
        v.dataset.protected = 'true';
        v.setAttribute('controlsList', 'nodownload');
        v.oncontextmenu = function() { return false; };
        v.addEventListener('contextmenu', function(e){ e.preventDefault(); return false; }, false);
      });
    }

    function initSubtitles() {
      var target = wrapper ? wrapper : document;
      var video = target.querySelector('video, audio');
      var listContainer = target.querySelector('details > p');
      
      if (!video) return false; 
      
      if (video.dataset.subInit === 'true') return true; 
      video.dataset.subInit = 'true';

      var oldOverlay = target.querySelector('#subtitleOverlay') || target.querySelector('#scSubtitleOverlay');
      if (oldOverlay) {
          oldOverlay.style.setProperty('display', 'none', 'important');
          oldOverlay.innerHTML = ''; 
      }

      function isSpecialMode() {
        var isFs = !!(document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement);
        var isPiP = !!(document.pictureInPictureElement && document.pictureInPictureElement === video) || (video.webkitPresentationMode === "picture-in-picture");
        var isIOSFs = !!video.webkitDisplayingFullscreen; 
        return isFs || isPiP || isIOSFs;
      }

      // 初期状態のセット（リストがなければ最初から showing に）
      try {
        if(video.textTracks && video.textTracks.length > 0){
          for(var i=0; i<video.textTracks.length; i++){
             if(video.textTracks[i].kind === 'subtitles' || video.textTracks[i].kind === 'captions'){
                 video.textTracks[i].mode = (!listContainer) ? "showing" : "hidden";
             }
          }
        }
      } catch(e){}

      // 字幕一覧が存在する場合のみ、リストのフォーマット処理を行う
      if (listContainer) {
          var detailsEl = target.querySelector("details");
          if (detailsEl) {
              detailsEl.open = true; 
              var summaryEl = detailsEl.querySelector("summary");
              if (summaryEl) summaryEl.textContent = "字幕(シーン)はここをクリック";
          }

          if (!listContainer.dataset.formatted) {
              var html = listContainer.innerHTML;
              var lines = html.split(/<br\s*\/?>/i);
              var newHtml = "";
              for(var j=0; j<lines.length; j++) {
                  if(lines[j].trim() === "") continue;
                  newHtml += "<span class='imk-line'>" + lines[j] + "</span><br>";
              }
              listContainer.innerHTML = newHtml;
              listContainer.dataset.formatted = "true";
          }

          var rootSublist = target.querySelector(".dr5-sublist") || listContainer.parentElement;
          if (rootSublist) {
            rootSublist.addEventListener("click", function(e){
              var a = e.target.closest && e.target.closest("a.imk-cue[data-seek]");
              if(!a) return;
              e.preventDefault();
              var ts = a.getAttribute("data-seek");
              if(!ts) return;
              var p = ts.trim().split(":").map(function(x){return parseInt(x,10)||0;});
              var sec = (p.length===2) ? p[0]*60+p[1] : (p.length===3 ? p[0]*3600+p[1]*60+p[2] : null);
              if(sec==null) return;
              try{ video.currentTime = sec; if(video.paused) video.play(); }catch(_){}
            });
          }
      }

      // ★ dr52.php と全く同じ「常時監視」のロジック
      // これにより、Safariが読み込み遅延を起こしても確実にONになります
      video.addEventListener("timeupdate", function(){
        var hasList = (listContainer !== null);
        var desiredMode = (isSpecialMode() || !hasList) ? "showing" : "hidden";
        
        try {
            if(video.textTracks && video.textTracks.length > 0){
                for(var i=0; i<video.textTracks.length; i++){
                    if((video.textTracks[i].kind === 'subtitles' || video.textTracks[i].kind === 'captions') && video.textTracks[i].mode !== desiredMode) {
                        video.textTracks[i].mode = desiredMode;
                    }
                }
            }
        } catch(e){}

        // リストがない場合はこれ以降のハイライト処理をスキップ
        if (!hasList) return;

        var cues = listContainer.querySelectorAll("a.imk-cue");
        if(cues.length === 0) return;
        var cur = video.currentTime;
        var activeA = null;

        for(var i=0; i<cues.length; i++){
            var ts = cues[i].getAttribute("data-seek");
            if(!ts) continue;
            var p = ts.trim().split(":").map(function(x){return parseInt(x,10)||0;});
            var t = (p.length===2) ? p[0]*60+p[1] : (p.length===3 ? p[0]*3600+p[1]*60+p[2] : null);
            if(t !== null && cur >= t - 0.5){ activeA = cues[i]; } 
            else if(t > cur){ break; }
        }

        if(activeA){
            var activeLine = activeA.closest(".imk-line");
            if(!activeLine) activeLine = activeA;

            if(activeLine.classList.contains("active-hl")) return;

            var allLines = listContainer.querySelectorAll(".imk-line");
            for(var k=0; k<allLines.length; k++) {
                allLines[k].classList.remove("active-hl");
                allLines[k].removeAttribute("style"); 
            }
            var allLinks = listContainer.querySelectorAll("a");
            for(var m=0; m<allLinks.length; m++) {
                allLinks[m].classList.remove("active-hl");
                allLinks[m].removeAttribute("style"); 
            }

            activeLine.classList.add("active-hl");
            activeLine.style.setProperty("background-color", "#ffff00", "important");
            activeLine.style.setProperty("color", "red", "important");
            activeLine.style.setProperty("font-weight", "normal", "important");
            
            var newLinks = activeLine.querySelectorAll("a");
            for(var n=0; n<newLinks.length; n++) {
                newLinks[n].style.setProperty("color", "red", "important");
                newLinks[n].style.setProperty("text-decoration", "none", "important");
            }

            if(listContainer.offsetParent !== null){
                var containerRect = listContainer.getBoundingClientRect();
                var activeRect = activeLine.getBoundingClientRect();
                var targetScroll = listContainer.scrollTop + (activeRect.top - containerRect.top) - (listContainer.clientHeight / 2) + (activeLine.clientHeight / 2);
                listContainer.scrollTo({ top: targetScroll, behavior: "smooth" });
            }
        }
      });

      return true;
    }

    var checks = 0;
    var checkTimer = setInterval(function(){
      protectVideo();
      var success = initSubtitles();
      checks++;
      if (success || checks > 20) { 
        clearInterval(checkTimer);
      }
    }, 500); 

    if (!window.scStopAndGo) {
      window.scStopAndGo = function(event, link){
        try{
          var mediaEls = document.querySelectorAll('video, audio');
          mediaEls.forEach(function(m){
            try{
              if (!m.paused) m.pause();
              if (document.pictureInPictureElement === m && document.exitPictureInPicture) {
                document.exitPictureInPicture().catch(function(){});
              }
            }catch(e){}
          });
        }finally{
          event.preventDefault();
          setTimeout(function(){
            if (link.target === '_blank') {
              window.open(link.href, '_blank');
            } else {
              window.location.href = link.href;
            }
          }, 50);
        }
        return false;
      };
    }
  })();
  </script>
</div>



<h3 class="wp-block-heading"><strong>動画で紹介している2つのシステム</strong></h3>



<p class="wp-block-paragraph">動画内で解説しているのは、私がローカルの <strong>Mac mini</strong> 上で構築・稼働させている以下の2つのシステムです。</p>



<h4 class="wp-block-heading"><strong>1. 配信を自由自在に操る「マイライブラリ」</strong></h4>



<p class="wp-block-paragraph">Dropboxをデータの「製造工場」として活用し、ウェブ配信用の素材（動画、画像、VTT字幕、書類など）をシームレスに配信所へ渡すシステムです。</p>



<ul class="wp-block-list">
<li><strong>複数の配信所を管理:</strong> 複数の配信サーバーを用意しておき、リクエストや用途に応じて自由自在に配信先を切り替えることが可能です。</li>
</ul>



<h4 class="wp-block-heading"><strong>2. NASの弱点を克服する「棚田式ファイル管理システム」</strong></h4>



<p class="wp-block-paragraph">一般的なNAS（RAID構成など）は、専用のフォーマットが必要なため「HDDを直接取り外して別のPCですぐに読み込む」といった物理的な使い回しが難しいという弱点があります。そこを解決したのがこのシステムです。</p>



<ul class="wp-block-list">
<li><strong>抜き差ししてすぐ使える:</strong> 一般的な外付けHDDやSSDと同じ感覚で、パコッと抜いて他のPCやMacに挿せば、すぐにデータを読み込めます。</li>



<li><strong>階層型（棚田式）の管理:</strong> データを「作業段」「保管段」「長期保管段」という3つの階層に分けて管理。使わず眠っている古いハードディスクも有効活用できます。</li>
</ul>



<h3 class="wp-block-heading"><strong>2つのシステムに共通する「最大の秘密」</strong></h3>



<p class="wp-block-paragraph">用途の異なるこの2つのシステムですが、設計の根底には共通する重要なコア技術があります。それは、**「<strong>メタデータの変更に基づき、実体データを操作する</strong>」**という仕組みです。</p>



<ul class="wp-block-list">
<li><strong>棚田式システムでの活用:</strong><br>いきなり重い実体データを動かすのではなく、まずは「メタデータ」だけを移動させるシミュレーションを何度も実行します。これにより、移動先の容量オーバーや操作ミスを事前に防ぐことができます。</li>



<li><strong>マイライブラリでの活用:</strong><br>スプレッドシートに登録した「属性（メタデータ）」を書き換えるだけで、それに連動して実際のファイルが自動で移動したり、再生サーバーが切り替わったりします。<br><br>もともとマイライブラリは、図書館をイメージしていて、図書館の本は質量のある物体のため、それぞれの図書館に受付窓口が必要になりますが、メディアアセットは、質量のない、ほぼ瞬間コピー、瞬間移動ができる存在であるため、図書館カードのようなメタデータは１箇所に集中することができるわけです。その１箇所でメタデータを変更することにより実体データを操作できるわけですね。</li>
</ul>



<h3 class="wp-block-heading"><strong>まとめ</strong></h3>



<p class="wp-block-paragraph">大容量のハードディスクが高価になっている今、手元にあるストレージをいかに安全・確実、そして無駄なく使い切るかは大きなテーマです。</p>



<p class="wp-block-paragraph">「メタデータによる実体データの制御」を取り入れることで、日々のクリエイティブ作業やデータ管理は驚くほど快適になります。ご自身のファイル管理や配信のヒントにしてみてください。</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">28734</post-id>	</item>
	</channel>
</rss>
