<?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>vtt | imakat.com</title>
	<atom:link href="https://imakat.com/tag/vtt/feed/" rel="self" type="application/rss+xml" />
	<link>https://imakat.com</link>
	<description>工夫と改善で人生をちょっと豊かに</description>
	<lastBuildDate>Thu, 08 Oct 2026 19:32:20 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.3</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>vtt | 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[vtt]]></category>
		<category><![CDATA[マルチモーダル]]></category>
		<guid isPermaLink="false">https://imakat.com/?p=29722</guid>

					<description><![CDATA[１　はじめに&#160; 26.10.03:エディタの改造を行ないました。内容としては、BGMや音声ファイルを合成する機能を追加しました。 今回の投稿は、前回の投稿ID-29651「VTT字幕の全自動生成」とペアになる投 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">１　<strong>はじめに</strong>&nbsp;</h2>



<p class="wp-block-paragraph">26.10.03:<a href="https://imakat.com/rd.php?id=w7wIty8o.png" target="_blank">エディタの改造</a>を行ないました。内容としては、BGMや音声ファイルを合成する機能を追加しました。</p>



<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="/ds62/?drid=105" target="_blank"
         onclick="return scStopAndGo(event, this);">
        👉低画質・枠外字幕はこちら
      </a>
    </p>
  </div>

  <!-- ★環境依存排除：[dynamic_external_html] にルート相対パスを指定し、functions.phpにURL動的解決を委ねる設計 -->
  <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 = ''; 
      }

      // 全画面・PiP・iOS全画面の判定（dr52.php準拠＋Safari presentationMode完全対応）
      function isSpecialMode() {
        var fsEl = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
        var isFs = !!fsEl;
        var isWebkitFs = (video.webkitPresentationMode === "fullscreen") || !!video.webkitDisplayingFullscreen;
        var isPiP = !!(document.pictureInPictureElement && document.pictureInPictureElement === video) || (video.webkitPresentationMode === "picture-in-picture");
        return isFs || isWebkitFs || isPiP;
      }

      // 字幕トラックの表示/非表示を更新
      function updateTrackMode() {
        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++) {
              var track = video.textTracks[i];
              if ((track.kind === 'subtitles' || track.kind === 'captions') && track.mode !== desiredMode) {
                track.mode = desiredMode;
              }
            }
          }
        } catch(e) {}
      }

      // 初回の反映
      updateTrackMode();

      // 各種全画面・PiP切り替えイベントの即時検知リスナーを登録（video本体・document双方を監視）
      ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'].forEach(function(evtName) {
        document.addEventListener(evtName, updateTrackMode, false);
        video.addEventListener(evtName, updateTrackMode, false);
      });
      video.addEventListener('webkitbeginfullscreen', updateTrackMode, false);
      video.addEventListener('webkitendfullscreen', updateTrackMode, false);
      video.addEventListener('webkitpresentationmodechanged', updateTrackMode, false);
      video.addEventListener('enterpictureinpicture', updateTrackMode, false);
      video.addEventListener('leavepictureinpicture', updateTrackMode, false);
      video.addEventListener('play', updateTrackMode, false);
      video.addEventListener('pause', updateTrackMode, false);

      // 字幕一覧が存在する場合のみ、リストのフォーマット処理を行う
      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(_){}
            });
          }
      }

      // 再生中（時間進行時）の追従とハイライト更新
      video.addEventListener("timeupdate", function(){
        updateTrackMode();

        var hasList = (listContainer !== null);
        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以降) を搭載した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-202610030832480" class="toggle-checkbox" type="checkbox"/><label class="toggle-button" for="toggle-checkbox-202610030832480"><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>「字幕かんたんエディタ」は、PyWebViewとBottleで構築されたmacOS向け字幕編集アプリケーションです。動画への字幕付け、BGMの追加・合成などの機能を持ち、WebKitのファイルスキーム制約回避やFFmpeg連携による効率的な処理を実現しています。配布は<code>build_dmg.sh</code>スクリプトで生成されるDMGパッケージを通じて行われ、<code>readme.txt</code>には初回起動や日常的な動画ファイルからの起動方法が案内されています。<br>本システムは、Gemini 3.6 Flash APIを利用した高度な自動字幕生成ワークフローも提供します。<code>multimodal_vtt_pipeline_auto.py</code>スクリプトは、入力された長尺動画をFFmpegで精密に分割し、Gemini APIで高精度なWebVTT形式の字幕を生成。同時に、ウェブページ埋め込み用の字幕一覧HTML（_listJ.txt）も自動作成します。このスクリプトは、処理完了後に「字幕かんたんエディタ.app」に動画パスを渡して自動起動し、シームレスな編集フローを実現します。<br>エディタ連携を伴わない単体で字幕を生成する<code>multimodal_vtt_pipeline.py</code>スクリプトも存在し、日本語の不自然な空白除去や話者区分の自動判断なども行います。<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; 26.10.09: Google無料アカウントのユーザーは本日からFlash-Liteのみに制限されるようです。変化が目まぐるしくて困ります。下の記事は「そんなことできた時があったんだ！」と驚か [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">１　<strong>はじめに</strong>&nbsp;</h2>



<p class="wp-block-paragraph">26.10.09: Google無料アカウントのユーザーは本日からFlash-Liteのみに制限されるようです。変化が目まぐるしくて困ります。下の記事は「そんなことできた時があったんだ！」と驚かれる内容かもしれません。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<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-202610090432070" class="toggle-checkbox" type="checkbox"/><label class="toggle-button" for="toggle-checkbox-202610090432070"><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>「字幕かんたん作成」〜無音声動画に解説字幕をサクッと付ける方法(Automator+AppleScript)</title>
		<link>https://imakat.com/2025/08/28/27063/</link>
		
		<dc:creator><![CDATA[imakat]]></dc:creator>
		<pubDate>Thu, 28 Aug 2025 09:32:54 +0000</pubDate>
				<category><![CDATA[字幕作成]]></category>
		<category><![CDATA[デジタル]]></category>
		<category><![CDATA[ものづくり]]></category>
		<category><![CDATA[vtt]]></category>
		<category><![CDATA[QuickTimePlayer]]></category>
		<category><![CDATA[TextEdit]]></category>
		<category><![CDATA[HUD]]></category>
		<guid isPermaLink="false">https://imakat.com/?p=27063</guid>

					<description><![CDATA[これまで動画の字幕作成については、何度か記事を投稿してきましたが、その中で感じた課題のいくつかを克服するアプリを、自作しましたので紹介します。 別の投稿で紹介したいと思いますが、字幕起こしを伴う字幕作成サービスについては [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">これまで動画の字幕作成については、何度か記事を投稿してきましたが、その中で感じた課題のいくつかを克服するアプリを、自作しましたので紹介します。</p>



<p class="wp-block-paragraph">別の投稿で紹介したいと思いますが、字幕起こしを伴う字幕作成サービスについては日進月歩でして、どんどん高品質になっています。大きく分けると、ローカルで処理する方法（大抵は無料でできる）と外部サーバーで処理する方法（大抵は使用量課金かサブスク提供)がありまして、外部サーバー利用の方については激しい競争下にあるためかなり高品質で、生成AI系企業のサービスが群を抜いています。</p>



<p class="wp-block-paragraph">最近、AIの進展で、動画をアップロードするだけで音声を取り出しその内容をAIに理解させることができるようになってはきましたが、やはり字幕ファイルによる文字情報で内容を理解させる方が圧倒的に迅速で正確です。しかも動画からの読み込みは多大なリソースを消費するので、追加料金も発生するかも知れません。そんな意味で、ブログ投稿をAIに学習させる場合も、動画には字幕ファイルを別添して貼り付けるようにして、字幕ファイルからの情報吸い上げを重視すべきだと私は思います。</p>



<p class="wp-block-paragraph">そんな中で、ここで紹介する方法は、<strong>無音声の動画に解説用の字幕をサクッと付ける</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="/ds62/?drid=58_2" target="_blank"
         onclick="return scStopAndGo(event, this);">
        👉低画質・枠外字幕はこちら
      </a>
    </p>
  </div>

  <!-- ★環境依存排除：[dynamic_external_html] にルート相対パスを指定し、functions.phpにURL動的解決を委ねる設計 -->
  <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=TnfyWc5U.png" playsinline preload="none" style="width:100%;height:auto;">  <source src="https://imakat.com/rd.php?id=PjGsFuO9.mp4" type="video/mp4">  <track src="https://imakat.com/rd.php?id=6oBnT3B3.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>) 1) 〜字幕かんたん作成〜<br>
(<a href="#" class="imk-cue" data-seek="0:20" translate="no">00:00:20</a>) 2) 今回は、Macで簡単に、動画の字幕ファイルを作成できる『字幕かんたん作成』これを紹介します。<br>
(<a href="#" class="imk-cue" data-seek="0:35" translate="no">00:00:35</a>) 3) 動画に字幕を付けるのに、編集ソフトで、コツコツと、タイムスタンプを入れるのは、とても大変ですよね。<br>
(<a href="#" class="imk-cue" data-seek="0:51" translate="no">00:00:51</a>) 4) このアプリを使えば、QuickTimePlayerで動画を再生しながら、簡単に、字幕を打ち込んでいけます。<br>
(<a href="#" class="imk-cue" data-seek="1:00" translate="no">00:01:00</a>) 5) お金は掛かりません。<br>
(<a href="#" class="imk-cue" data-seek="1:03" translate="no">00:01:03</a>) 6) Macに備わった標準アプリだけで動きます。<br>
(<a href="#" class="imk-cue" data-seek="1:12" translate="no">00:01:12</a>) 7) 通常の字幕作成では、動画の再生時間を確認して、開始時刻と終了時刻を、VTTファイルへ入力します。<br>
(<a href="#" class="imk-cue" data-seek="1:27" translate="no">00:01:27</a>) 8) サンプルをお見せします。<br>
(<a href="#" class="imk-cue" data-seek="1:33" translate="no">00:01:33</a>) 9) 例えば、このような文字列があります。<br>
(<a href="#" class="imk-cue" data-seek="1:37" translate="no">00:01:37</a>) 10) これはVTTファイルです。<br>
(<a href="#" class="imk-cue" data-seek="1:41" translate="no">00:01:41</a>) 11) 最初に、順番が入ります。　　<br>
(<a href="#" class="imk-cue" data-seek="1:46" translate="no">00:01:46</a>) 12) 次に、時　分　秒　ミリ秒、この基準に従った開始時刻<br>
(<a href="#" class="imk-cue" data-seek="1:54" translate="no">00:01:54</a>) 13) それと、同じくその基準の終了時刻を入れます。<br>
(<a href="#" class="imk-cue" data-seek="2:01" translate="no">00:02:01</a>) 14) 真ん中のところに、スペース１つ、--不等号 またひとつスペースを入れます。<br>
(<a href="#" class="imk-cue" data-seek="2:13" translate="no">00:02:13</a>) 15) その下にこうしたテキスト行を入れます。<br>
(<a href="#" class="imk-cue" data-seek="2:19" translate="no">00:02:19</a>) 16) これがVTT記述の基本形です。<br>
(<a href="#" class="imk-cue" data-seek="2:25" translate="no">00:02:25</a>) 17) このように自分でTextEditに打てば、正確にできるのですが、　　<br>
(<a href="#" class="imk-cue" data-seek="2:34" translate="no">00:02:34</a>) 18) 手間が多く、時間もかかってしまいます。<br>
(<a href="#" class="imk-cue" data-seek="2:43" translate="no">00:02:43</a>) 19) そこで開発したのが、この『字幕かんたん作成』です。<br>
(<a href="#" class="imk-cue" data-seek="2:50" translate="no">00:02:50</a>) 20) AutomatorとAppleScript_Objective-Cを組み合わせて、<br>
(<a href="#" class="imk-cue" data-seek="2:57" translate="no">00:02:57</a>) 21) QuickTimePlayerとTextEditを連携させる、<br>
(<a href="#" class="imk-cue" data-seek="3:03" translate="no">00:03:03</a>) 22) そのことで、シンプルに、効率よく、字幕ファイルを作ることができます。<br>
(<a href="#" class="imk-cue" data-seek="3:14" translate="no">00:03:14</a>) 23) その仕組みをフロー図によって説明します。<br>
(<a href="#" class="imk-cue" data-seek="3:29" translate="no">00:03:29</a>) 24) まず、FinderでVTTファイルを右クリックします。<br>
(<a href="#" class="imk-cue" data-seek="3:41" translate="no">00:03:41</a>) 25) するとクイックアクションの中にある、今回のアプリ 「字幕かんたん作成」が起動します。<br>
(<a href="#" class="imk-cue" data-seek="3:51" translate="no">00:03:51</a>) 26) スクリプトが動作し、黄色の字幕表示画面(HUDと呼びますが)それと、　<br>
(<a href="#" class="imk-cue" data-seek="4:00" translate="no">00:04:00</a>) 27) 入力Boxが表示されます。<br>
(<a href="#" class="imk-cue" data-seek="4:09" translate="no">00:04:09</a>) 28) 字幕の入力先は、macOS標準のTextEditを使います。 <br>
(<a href="#" class="imk-cue" data-seek="4:17" translate="no">00:04:17</a>) 29) 入力したテキストは、QuickTimePlayerの再生位置の時刻、タイムスタンプ、それらと一緒にVTTファイルに記入されます。<br>
(<a href="#" class="imk-cue" data-seek="4:33" translate="no">00:04:33</a>) 30) QuickTimePlayerは再生位置を提供し、NSTimerが、それに対応する時刻を、定期的にゲットします。　<br>
(<a href="#" class="imk-cue" data-seek="4:48" translate="no">00:04:48</a>) 31) 終了する場合は、終了ボタンを押すと、タイマーが止まり、HUDが閉じ、メモリーが解放されて、処理が終了します。<br>
(<a href="#" class="imk-cue" data-seek="5:08" translate="no">00:05:08</a>) 32) それでは、実際の動きをご覧ください。 <br>
(<a href="#" class="imk-cue" data-seek="5:13" translate="no">00:05:13</a>) 33) 動画の例として、「ガラポンTVの故障」を取り上げます。<br>
(<a href="#" class="imk-cue" data-seek="5:21" translate="no">00:05:21</a>) 34) 一度、動画だけをみてください。（約1分） <br>
(<a href="#" class="imk-cue" data-seek="6:28" translate="no">00:06:28</a>) 35) 動画を見ただけでは、何を伝えたいのか、さっぱりわかりませんね。<br>
(<a href="#" class="imk-cue" data-seek="6:36" translate="no">00:06:36</a>) 36) そこで、字幕を、作成していきます。<br>
(<a href="#" class="imk-cue" data-seek="6:43" translate="no">00:06:43</a>) 37) それでは、作業をやってみます。<br>
(<a href="#" class="imk-cue" data-seek="6:49" translate="no">00:06:49</a>) 38) まず、動画を右上の方に置いておきます。<br>
(<a href="#" class="imk-cue" data-seek="6:57" translate="no">00:06:57</a>) 39) すでにvttファイル名は付けてありますが、中身をクリアして作業をしてみます。<br>
(<a href="#" class="imk-cue" data-seek="7:05" translate="no">00:07:05</a>) 40) このvttファイルの上で、右クリックして、クイックアクションで、「字幕かんたん作成」を立ち上げます。<br>
(<a href="#" class="imk-cue" data-seek="7:32" translate="no">00:07:32</a>) 41) こんなように立ち上がります。<br>
(<a href="#" class="imk-cue" data-seek="7:36" translate="no">00:07:36</a>) 42) このHUDをQuickTimePlayerの下に置いて、　<br>
(<a href="#" class="imk-cue" data-seek="7:42" translate="no">00:07:42</a>) 43) 実際に、テキストを入れていきます。<br>
(<a href="#" class="imk-cue" data-seek="8:03" translate="no">00:08:03</a>) 44) ガラポンTVが異常です。全ランプが点滅を繰り返しています。<br>
(<a href="#" class="imk-cue" data-seek="8:18" translate="no">00:08:18</a>) 45) このように打ってOKを押します。<br>
(<a href="#" class="imk-cue" data-seek="8:26" translate="no">00:08:26</a>) 46) 継続秒数を入れます。<br>
(<a href="#" class="imk-cue" data-seek="8:41" translate="no">00:08:41</a>) 47) 1番目が出来上がります。<br>
(<a href="#" class="imk-cue" data-seek="8:55" translate="no">00:08:55</a>) 48) 背面の電源をOFFにしました。<br>
(<a href="#" class="imk-cue" data-seek="9:11" translate="no">00:09:11</a>) 49) OK<br>
(<a href="#" class="imk-cue" data-seek="9:13" translate="no">00:09:13</a>) 50) 4秒<br>
(<a href="#" class="imk-cue" data-seek="9:17" translate="no">00:09:17</a>) 51) 2番目が入ります。<br>
(<a href="#" class="imk-cue" data-seek="9:20" translate="no">00:09:20</a>) 52) HUDにも表示されます。<br>
(<a href="#" class="imk-cue" data-seek="9:40" translate="no">00:09:40</a>) 53) 外付けHDDを外しました。<br>
(<a href="#" class="imk-cue" data-seek="10:02" translate="no">00:10:02</a>) 54) 前の部分で、説明を入れるのを忘れたとしても、戻ることができます。<br>
(<a href="#" class="imk-cue" data-seek="10:16" translate="no">00:10:16</a>) 55) この場所で、ランプは消えました、と入れます。<br>
(<a href="#" class="imk-cue" data-seek="10:35" translate="no">00:10:35</a>) 56) 3番目として、ランプが消えた、が飛び込みます。<br>
(<a href="#" class="imk-cue" data-seek="10:46" translate="no">00:10:46</a>) 57) このような形で、入力が終了し、再生した時に、字幕がタイミングよく表示されることを確認する必要があります。その後で実際は終了します。<br>
(<a href="#" class="imk-cue" data-seek="11:02" translate="no">00:11:02</a>) 58) それでは、動画と、完成した字幕を、同時に表示してみましょう。<br>
(<a href="#" class="imk-cue" data-seek="11:17" translate="no">00:11:17</a>) 59) 字幕があることによって、その、ガラポンTVが故障している、そのように判断した理由が、より明確に、伝わるようになります。<br>
(<a href="#" class="imk-cue" data-seek="12:10" translate="no">00:12:10</a>) 60) この後、ガラポンTV社に報告して、迅速に、交換が完了しました。<br>
(<a href="#" class="imk-cue" data-seek="12:24" translate="no">00:12:24</a>) 61) このように『字幕かんたん作成』は、AutomatorとAppleScript_Objective-Cを使って、<br>
(<a href="#" class="imk-cue" data-seek="12:33" translate="no">00:12:33</a>) 62) QuickTimePlayerとTextEditを組み合わせることによって、 　<br>
(<a href="#" class="imk-cue" data-seek="12:40" translate="no">00:12:40</a>) 63) 面倒な字幕作成を、シンプルに、効率的に、してくれます。 <br>
(<a href="#" class="imk-cue" data-seek="12:46" translate="no">00:12:46</a>) 64) このスクリプトは、ブログ内で公開します。<br>
(<a href="#" class="imk-cue" data-seek="12:50" translate="no">00:12:50</a>) 65) 字幕を、できるだけ手軽に付けたい、そう思う方は、ぜひ活用してみてください。<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 = ''; 
      }

      // 全画面・PiP・iOS全画面の判定（dr52.php準拠＋Safari presentationMode完全対応）
      function isSpecialMode() {
        var fsEl = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
        var isFs = !!fsEl;
        var isWebkitFs = (video.webkitPresentationMode === "fullscreen") || !!video.webkitDisplayingFullscreen;
        var isPiP = !!(document.pictureInPictureElement && document.pictureInPictureElement === video) || (video.webkitPresentationMode === "picture-in-picture");
        return isFs || isWebkitFs || isPiP;
      }

      // 字幕トラックの表示/非表示を更新
      function updateTrackMode() {
        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++) {
              var track = video.textTracks[i];
              if ((track.kind === 'subtitles' || track.kind === 'captions') && track.mode !== desiredMode) {
                track.mode = desiredMode;
              }
            }
          }
        } catch(e) {}
      }

      // 初回の反映
      updateTrackMode();

      // 各種全画面・PiP切り替えイベントの即時検知リスナーを登録（video本体・document双方を監視）
      ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'].forEach(function(evtName) {
        document.addEventListener(evtName, updateTrackMode, false);
        video.addEventListener(evtName, updateTrackMode, false);
      });
      video.addEventListener('webkitbeginfullscreen', updateTrackMode, false);
      video.addEventListener('webkitendfullscreen', updateTrackMode, false);
      video.addEventListener('webkitpresentationmodechanged', updateTrackMode, false);
      video.addEventListener('enterpictureinpicture', updateTrackMode, false);
      video.addEventListener('leavepictureinpicture', updateTrackMode, false);
      video.addEventListener('play', updateTrackMode, false);
      video.addEventListener('pause', updateTrackMode, false);

      // 字幕一覧が存在する場合のみ、リストのフォーマット処理を行う
      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(_){}
            });
          }
      }

      // 再生中（時間進行時）の追従とハイライト更新
      video.addEventListener("timeupdate", function(){
        updateTrackMode();

        var hasList = (listContainer !== null);
        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">ポイント解説</h3>



<p class="wp-block-paragraph"><strong>１　VTTで編集ができます</strong>：<br>簡易な字幕作成ツールとしては、「字幕工房」が優秀であるという見方は変わりませんが、SRTファイルでの書き出しです。私はWordPressブログに掲載する動画が殆どですので、毎回VTTファイルへ変換することになり面倒です。「字幕工房」に限らず、動画作成アプリで出力される字幕は、殆どがSRTファイルなのですね。</p>



<p class="wp-block-paragraph"><strong>２　ローカルファイルの更新をそのまま反映します</strong>：<br>動画共有サービスに共通することですが、字幕もそのほかのメディアアセットもローカルからアップロードした時点で切り離されてしまうので、ローカルファイルを更新しても自動では反映されません。図の下段は、今運用しているマイ・メディアライブラリの機能ですが、ここでもその威力が発揮されます。ローカルでVTTファイルをTextEditで開いて手直しすれば、そのまま自動的に、ブログの表示が変更されます（数分のタイムラグはあり）。</p>



<a rel="noopener" target="_blank" href="https://docs.google.com/drawings/d/1GXuQ_5Vtufv7YH8_SwkfyAMHxzctFyzNb4J4dmJ-MDc/edit?usp=sharing" 
>
<img decoding="async" src="https://docs.google.com/drawings/d/e/2PACX-1vSpM7uCAK9FehOqeogKeJoeCMs1929xNU45hsx1tk_CUlhO9S8CH30BgcnNMx4URJ6nH83dm9uGP2lS/pub?w=1440&#038;h=1080"
></a>



<p class="wp-block-paragraph"><strong>３　停止位置の時刻を自動で取得します</strong>：<br>QuickTimePlayerで動画を停止した場所の時刻(時点)を自動で取得して、テキストとともに、VTTファイルへ書き込みます。さらに、書き込んだ情報を読み込んで、HUD(ディスプレイ)に即座に表示します。</p>



<p class="wp-block-paragraph">主な特徴点は以上ですが、かんたんな点では、「字幕工房」を上回っています。</p>



<p class="wp-block-paragraph"><strong>４　わずか2ステップ</strong>：</p>



<p class="wp-block-paragraph"><strong>①対象の動画をクリックして開く</strong>(デフォルトはQuickTimePlayer)。</p>



<p class="wp-block-paragraph"><strong>②フォルダの上で右クリックして、「字幕かんたん作成」を立ち上げ、VTTファイルを作る</strong>。</p>



<p class="wp-block-paragraph"><strong>だけ</strong>です。</p>



<p class="wp-block-paragraph"><strong>５　キビキビと動きます：</strong><br>字幕作成アプリを探すと、ブラウザアプリが主流になっています。その<strong>ブラウザアプリですが、欠点は、「もったりヌルヌルした動作</strong>」にあります。iPad iPhoneでは仕方ないにしても、Macでも感じるところがあるので、それはなんか疲れるわけですね。QuickTimePlayer、TextEdit、標準スクリプト、そういった標準搭載ツールは、実にキビキビと動きます。</p>



<h3 class="wp-block-heading">実装の手順</h3>



<figure class="wp-block-flexible-table-block-table"><table class="has-fixed-layout"><tbody><tr><td style="width:10%">(1)</td><td>下の青色のボタンから、スクリプト(txtファイル)をダウンロードします。</td></tr><tr><td style="width:10%">(2)</td><td>生成AI( Gemini、ChatGPTなど)を利用すると簡単です。AIのチャット欄に、<br>①ダウンロードしたスクリプトをアップロードします。<br>②以下を記述します。<br>「私のMacに、添付したスクリプトを実装する手順を作ってください。それをtxt形式で提供ください。<br>私のMacは、[機種名][OSバージョン]です。よろしくお願いします。」</td></tr><tr><td style="width:10%">(3)</td><td>提供された、実装手順を記載したtxtファイルに基づき、生成AIとやりとりしながら、実装してください。<br>※txtファイルを開いたまま生成AIとやり取りし、修正後のtxtを再度送って「これで大丈夫ですか」と確認する方法を取れば、更新内容を逐一手元に残せるため、作業の履歴管理として便利で安心です。</td></tr><tr><td colspan="2"><a href="https://imakat.com/?pubtxt=字幕かんたん作成workflow_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>



<p class="wp-block-paragraph">以上です。<br><br></p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">27063</post-id>	</item>
		<item>
		<title>字幕一覧(字幕リスト)は、古いようで一番先進の動画構成方法！</title>
		<link>https://imakat.com/2024/02/11/20002/</link>
		
		<dc:creator><![CDATA[imakat]]></dc:creator>
		<pubDate>Sat, 10 Feb 2024 19:42:46 +0000</pubDate>
				<category><![CDATA[字幕作成]]></category>
		<category><![CDATA[デジタル]]></category>
		<category><![CDATA[ものづくり]]></category>
		<category><![CDATA[Apple翻訳]]></category>
		<category><![CDATA[Windows翻訳]]></category>
		<category><![CDATA[Google翻訳]]></category>
		<category><![CDATA[Dropbox]]></category>
		<category><![CDATA[vimeo]]></category>
		<category><![CDATA[Appleスクリプト]]></category>
		<category><![CDATA[srt]]></category>
		<category><![CDATA[vtt]]></category>
		<category><![CDATA[タイムコード]]></category>
		<guid isPermaLink="false">https://imakat.com/?p=20002</guid>

					<description><![CDATA[現状では、世界のどこかの人が何かのデバイスで、このブログを見て、その中の動画を視聴した時に、言葉がわからないという問題にぶつかります。ですから、そのブログの中に、言葉をわかるようにする「手掛かり」を配置しておきたいと思い [&#8230;]]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large is-resized"><a href="https://imakat.com/rd.php?id=GjFF2Rw0.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=GjFF2Rw0.png" alt="" style="width:222px;height:auto"/></a><figcaption class="wp-element-caption">「字幕一覧」は本屋の立ち読み</figcaption></figure>



<p class="wp-block-paragraph">現状では、世界のどこかの人が何かのデバイスで、このブログを見て、その中の動画を視聴した時に、言葉がわからないという問題にぶつかります。ですから、そのブログの中に、<strong>言葉をわかるようにする「手掛かり」を配置</strong>しておきたいと思います。</p>



<p class="wp-block-paragraph">ただその「手掛かり」と言っても、なかなか簡単ではありません。<br>条件が複雑過ぎるからです。</p>



<p class="wp-block-paragraph">現在、条件がどうマチマチであるか眺めてみます。まず、デバイスとしてポピュラーなものをピックアップすると、WindowsPC,Mac,iPhone,iPad,Android携帯、Androidタブレット、以上になるでしょうし、OSでポピュラーなものは、Windows,Mac,iPad/iPhone,Androidになるでしょうし、ブラウザでポピュラーなものは、Chrome,Edge,Safariになるでしょうし、さらに動画の配信側は、YouTube,Vimeo,WordPress,Dropboxなどクラウドサービス、自宅サーバーなど。。<br>こんな複雑な条件の中で、今、こうしたブログさらにその中の動画などが配信されているわけですね。ですから、全てのユーザーに、「言葉がわかるようにする」のは無理と言っていいでしょう。</p>



<p class="wp-block-paragraph">結論が遅れました。</p>



<p class="wp-block-paragraph">それほど、手の込んだ工夫ではありません。</p>



<p class="wp-block-paragraph"><strong><span class="fz-18px"><span class="marker">動画の下に、タイムコードあるいは番号が付いた「字幕一覧」を配置すべし。<br>具体的には以下です。</span></span></strong><br></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="/ds62/?drid=28" target="_blank"
         onclick="return scStopAndGo(event, this);">
        👉低画質・枠外字幕はこちら
      </a>
    </p>
  </div>

  <!-- ★環境依存排除：[dynamic_external_html] にルート相対パスを指定し、functions.phpにURL動的解決を委ねる設計 -->
  <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=AWGsA7WG.png" playsinline preload="none" style="width:100%;height:auto;">  <source src="https://imakat.com/rd.php?id=WLISfxH7.mp4" type="video/mp4">  <track src="https://imakat.com/rd.php?id=QBzEiJtv.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>)  Synology NAS HDD「お下がり」交換リレー<br>
(<a href="#" class="imk-cue" data-seek="0:11" translate="no">00:00:11</a>)  これが自宅にあるNASとWebサーバーです。<br>
(<a href="#" class="imk-cue" data-seek="0:23" translate="no">00:00:23</a>)  DS220+ですけどね、最近容量不足ということで困ってました。<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:34" translate="no">00:00:34</a>)  ストレージマネージャーにこういう警告が出ています。<br>
(<a href="#" class="imk-cue" data-seek="0:39" translate="no">00:00:39</a>)  二巡すると全部が入れ替わるようになります。<br>
(<a href="#" class="imk-cue" data-seek="0:46" translate="no">00:00:46</a>)  事前にハードディスクとSATAスタンドを買っておきました。<br>
(<a href="#" class="imk-cue" data-seek="0:53" translate="no">00:00:53</a>)  それでは作業を具体的に見ていきましょう。NASの蓋を外します。<br>
(<a href="#" class="imk-cue" data-seek="1:04" translate="no">00:01:04</a>)  ディスク1とディスク2がありますが、同じ容量なんで2から抜くことにします。<br>
(<a href="#" class="imk-cue" data-seek="1:14" translate="no">00:01:14</a>)  引き抜きます。<br>
(<a href="#" class="imk-cue" data-seek="1:21" translate="no">00:01:21</a>)  Seagateの6TBです。<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:51" translate="no">00:01:51</a>)  右側が今外した6TBです。左が12TBの新品です。<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:21" translate="no">00:02:21</a>)  まあ滅多にやらない作業なんで手際が悪いです。<br>
(<a href="#" class="imk-cue" data-seek="2:45" translate="no">00:02:45</a>)  12TBの方にはめます。<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:58" translate="no">00:03:58</a>)  差し込みます。<br>
(<a href="#" class="imk-cue" data-seek="4:30" translate="no">00:04:30</a>)  ディスク2のライトが点灯しました。装着まではOKです。<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:54" translate="no">00:04:54</a>)  修復中となります。最初に初期化されます。<br>
(<a href="#" class="imk-cue" data-seek="5:03" translate="no">00:05:03</a>)  それでは3番目の作業で、Webサーバーの3TBの入れ替えをします。<br>
(<a href="#" class="imk-cue" data-seek="5:25" translate="no">00:05:25</a>)  Webサーバーの蓋を外します。<br>
(<a href="#" class="imk-cue" data-seek="5:31" translate="no">00:05:31</a>)  ここにディスク1とディスク2があります。同じ容量なのでどちらから抜いても構いません。<br>
(<a href="#" class="imk-cue" data-seek="5:40" translate="no">00:05:40</a>)  2から抜いてみます。<br>
(<a href="#" class="imk-cue" data-seek="5:53" translate="no">00:05:53</a>)  ホットスワップ機能がついているもんですから、ハードディスクを抜いてもサーバーは中断しないです。そのまま動作します。<br>
(<a href="#" class="imk-cue" data-seek="6:18" translate="no">00:06:18</a>)  ハードディスク取り付けパーツを、お下がりの6TBへ付け替えます。<br>
(<a href="#" class="imk-cue" data-seek="6:38" translate="no">00:06:38</a>)  どうも不器用です。<br>
(<a href="#" class="imk-cue" data-seek="7:01" translate="no">00:07:01</a>)  6TBの方へ付けます。<br>
(<a href="#" class="imk-cue" data-seek="7:11" translate="no">00:07:11</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:23" translate="no">00:08:23</a>)  6TBを差し込みます。<br>
(<a href="#" class="imk-cue" data-seek="8:47" translate="no">00:08:47</a>)  ディスク2が点灯しました。<br>
(<a href="#" class="imk-cue" data-seek="8:57" translate="no">00:08:57</a>)  お下がりの6TBは差し込み済みなので、今すぐ修復を選びます。<br>
(<a href="#" class="imk-cue" data-seek="9:06" translate="no">00:09:06</a>)  ディスク2を選びます。<br>
(<a href="#" class="imk-cue" data-seek="9:13" translate="no">00:09:13</a>)  お下がりなのでデータは入ってるんですね。まあ消していいか聞いてきますので、「いいえ」ではなく「はい」を選びます。<br>
(<a href="#" class="imk-cue" data-seek="9:33" translate="no">00:09:33</a>)  修復中になります。<br>
(<a href="#" class="imk-cue" data-seek="9:41" translate="no">00:09:41</a>)  一巡目の最後の作業ですね。お下がりの3TBをSATAスタンドに取り付けます。<br>
(<a href="#" class="imk-cue" data-seek="9:55" translate="no">00:09:55</a>)  私が買ったのはAlxumというスタンドです。<br>
(<a href="#" class="imk-cue" data-seek="10:20" translate="no">00:10:20</a>)  それではAのソケットに差し込みます。<br>
(<a href="#" class="imk-cue" data-seek="11:06" translate="no">00:11:06</a>)  Bも空いています。<br>
(<a href="#" class="imk-cue" data-seek="11:23" translate="no">00:11:23</a>)  まあ、硬いですな。<br>
(<a href="#" class="imk-cue" data-seek="11:33" translate="no">00:11:33</a>)  SATAスタンドのハードディスクはexFATでフォーマットします。<br>
(<a href="#" class="imk-cue" data-seek="11:41" translate="no">00:11:41</a>)  これで一巡目は終わりですけど、NASサーバー、Webサーバーともに修復順調に進んでます。<br>
(<a href="#" class="imk-cue" data-seek="11:53" translate="no">00:11:53</a>)  TimeMachineの設定については割愛しますね。<br>
(<a href="#" class="imk-cue" data-seek="11:58" translate="no">00:11:58</a>)  まあちょっと気分転換です。<br>
(<a href="#" class="imk-cue" data-seek="12:04" translate="no">00:12:04</a>)  家の近くにですね、タヌキが出ていました。<br>
(<a href="#" class="imk-cue" data-seek="12:10" translate="no">00:12:10</a>)  まあ本当に犬ですね。犬そっくりです。走り方も犬。<br>
(<a href="#" class="imk-cue" data-seek="12:20" translate="no">00:12:20</a>)  二巡目ですけど、やることは一巡目と同じです。<br>
(<a href="#" class="imk-cue" data-seek="12:29" translate="no">00:12:29</a>)  もう結果からお見せします。SATAスタンドに2個のハードディスクが刺さってます。<br>
(<a href="#" class="imk-cue" data-seek="12:43" translate="no">00:12:43</a>)  最後にマネージャーを確認しますと、NAS、Webサーバーともに修復中、進んでます。<br>
(<a href="#" class="imk-cue" data-seek="12:53" translate="no">00:12:53</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 = ''; 
      }

      // 全画面・PiP・iOS全画面の判定（dr52.php準拠＋Safari presentationMode完全対応）
      function isSpecialMode() {
        var fsEl = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
        var isFs = !!fsEl;
        var isWebkitFs = (video.webkitPresentationMode === "fullscreen") || !!video.webkitDisplayingFullscreen;
        var isPiP = !!(document.pictureInPictureElement && document.pictureInPictureElement === video) || (video.webkitPresentationMode === "picture-in-picture");
        return isFs || isWebkitFs || isPiP;
      }

      // 字幕トラックの表示/非表示を更新
      function updateTrackMode() {
        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++) {
              var track = video.textTracks[i];
              if ((track.kind === 'subtitles' || track.kind === 'captions') && track.mode !== desiredMode) {
                track.mode = desiredMode;
              }
            }
          }
        } catch(e) {}
      }

      // 初回の反映
      updateTrackMode();

      // 各種全画面・PiP切り替えイベントの即時検知リスナーを登録（video本体・document双方を監視）
      ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'].forEach(function(evtName) {
        document.addEventListener(evtName, updateTrackMode, false);
        video.addEventListener(evtName, updateTrackMode, false);
      });
      video.addEventListener('webkitbeginfullscreen', updateTrackMode, false);
      video.addEventListener('webkitendfullscreen', updateTrackMode, false);
      video.addEventListener('webkitpresentationmodechanged', updateTrackMode, false);
      video.addEventListener('enterpictureinpicture', updateTrackMode, false);
      video.addEventListener('leavepictureinpicture', updateTrackMode, false);
      video.addEventListener('play', updateTrackMode, false);
      video.addEventListener('pause', updateTrackMode, false);

      // 字幕一覧が存在する場合のみ、リストのフォーマット処理を行う
      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(_){}
            });
          }
      }

      // 再生中（時間進行時）の追従とハイライト更新
      video.addEventListener("timeupdate", function(){
        updateTrackMode();

        var hasList = (listContainer !== null);
        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-large"><a href="https://imakat.com/rd.php?id=MRuDPz44.png" target="_blank"><img decoding="async" src="https://imakat.com/rd.php?id=MRuDPz44.png" alt=""/></a></figure>



<p class="wp-block-paragraph">このような折りたたみの字幕一覧を用意しておくと、ブログの翻訳をすれば、この部分も翻訳されます。(1)(2)などの番号でもいいですが、動画の中の位置が分かりません。タイムコードなら動画のバーを近くに持っていけます。YouTubeなどで字幕の自動翻訳もありますが、<strong>速く進み過ぎて目が追いつけません</strong>。だから、自分のペースで追いたいです。逆に、<strong>急いでいるので動画の要点をサラッと知りたい</strong>ということもあります。<strong>いわば「本屋の立ち読み」です。音声の早送りより、はるかに生産性は高いです。10分の動画でも字幕一覧をみれば30秒で概略がわかります</strong>。</p>



<p class="wp-block-paragraph">AIの解読力ですが、動画を理解することもできるようになってきてはいます。しかし多大なリソース消費の問題もあり正確性も今一歩のところがあります。Geminiに質問してみましたが、動画に字幕ファイルが別添されている形が、AIとすると一番ありがたい形式、と言う返答でした。</p>



<p class="wp-block-paragraph">いずれにせよ字幕一覧形式は非常に優れています。翻訳した字幕一覧を自分のメモに貼り付けることも出来ます。字幕が邪魔なら、動画の字幕を非表示にして、この字幕一覧を折り畳めばいいです。</p>



<p class="wp-block-paragraph"><br>ちなみに、<strong>上の折りたたみ字幕一覧を、左上のGoogle翻訳で試してみてください。翻訳の途中から改行がされない現象が生ずる場合があります。これはChromeからChromeの翻訳機能を使っても同じ現象になるでしょう。<br>次に、AppleやWindowsの翻訳を試してみてください。ブラウザから利用できると思います。それを使うと、正しく改行されるはずです</strong>。</p>



<p class="wp-block-paragraph">Macで使う場合、Vimeoの動画に表示される字幕の翻訳は、Google翻訳では翻訳不可ですが、Apple翻訳ではVimeoの動画に表示される字幕を翻訳してくれます(<a href="https://imakat.com/vm/?movid=913935684">動画無音</a>)。iPad,iPhoneではそれが難しいのでWordPress固定ページに字幕一覧を読み込んで工夫することにします(<a href="https://imakat.com/vm/?movid=914144217">動画有音</a>)。</p>



<p class="wp-block-paragraph">Apple翻訳で気づいた欠点の一つは、WordPress動画プレーヤーで表示される字幕について翻訳しようとする時パカパカと点滅状態になるため(<a href="https://imakat.com/ds5/?drid=1">動画無音</a>)字幕は非表示にせざるを得ない点があります（2024.2月現在)。</p>



<p class="wp-block-paragraph">なお、WindowsのEdgeにより、vimeoのプレーヤーを直接に呼び出す場合ですが、AppleのSafariと同じく、字幕は直接に翻訳されます(<a href="https://imakat.com/vm?movid=914067338">動画無音</a>)。</p>



<p class="wp-block-paragraph">総じて、各社の字幕や翻訳の対応は、まだまだバラバラな状況と言っていいです。そこへ群がるように、各社がAI生成画像、音楽などのサービスをくっつけてきているというのが現状です。</p>



<p class="wp-block-paragraph">そこで、<strong><span class="marker">振り回されないように、字幕一覧を配置して、Apple/Windows翻訳とGoogle翻訳の両方を使い分けるようにしよう</span></strong>。というのが、この投稿の主張です。</p>



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



<hr class="wp-block-separator has-text-color has-key-color-color has-alpha-channel-opacity has-key-color-background-color has-background is-style-wide"/>



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



<p class="wp-block-paragraph"><strong><span class="fz-16px"><span class="fz-18px"><span class="marker">＜字幕作成に関するオリジナルスクリプト一覧表＞</span></span></span></strong></p>



<figure class="wp-block-flexible-table-block-table is-scroll-on-pc is-scroll-on-mobile"><table class="" style="width:740px;max-width:740px"><tbody><tr><td style="background-color:#f3fafe"><strong>スクリプト名</strong></td><td style="background-color:#f3fafe"><strong>説明</strong></td><td style="background-color:#f3fafe"><strong>補足</strong></td></tr><tr><td><a href="https://imakat.com/wp-content/pmedia/scripts_pub/srtからvttを生成するworkflow_pub.txt" target="_blank">srtからvttを生成する</a></td><td><a href="https://imakat.com/ds5/?drid=2">動画(無音)</a><br>新たに作ったVTTファイルに対して、 <br> (1) 先頭行に文字列&#8221;WEBVTT&#8221;を含む行を挿入する。 <br>(2) 2行目に空白の行を挿入する。 <br>(3) 3行目以降から、読み込んだデータを書き込む。 <br> (4) &#8220;&#8211;&gt;&#8221; がある行は、行の中の&#8221;,&#8221;を&#8221;.&#8221;に置き換える。<br>※元のファイルを残します。</td><td>AppleScriptクイックアクション</td></tr><tr><td><a href="https://imakat.com/wp-content/pmedia/scripts_pub/vttからsrtを生成する兼srtの連番を付け直すworkflow_pub.txt" target="_blank">vttからsrtを生成する兼srtの連番を付け直す</a>(renumbered.srt)</td><td><a href="https://imakat.com/ds5?drid=3">動画(無音</a>)<br>新たに作ったSRTファイルに対して、<br>(1) 文字列”WEBVTT”のある行は削除する。<br>(2) &#8220;&#8211;&gt;&#8221; がある行は、行の中の&#8221;.&#8221;を&#8221;,&#8221;に置き換える。<br>(3)連番を1から振り直す。<br>※元のファイルを残します。新しいファイル名は、&#8221;元のファイル名_renumbered.srt&#8221;となります。</td><td>AppleScriptクイックアクション</td></tr><tr><td><a href="https://imakat.com/wp-content/pmedia/scripts_pub/字幕文章に時刻を挿入するworkflow_pub.txt" target="_blank">字幕文章に時刻を挿入する</a>(timeadded.vtt)</td><td><a href="https://imakat.com/ds5?drid=4">動画(無音</a><a href="https://imakat.com/ds?drid=4">)</a><br>文字列の先頭に、開始時刻(00:01:23)を追記する。私はカモメ→(00:01:23)私はカモメ※元のファイルを残します。_timeadded.vttが新しく作られます。この字幕文章の先頭に時刻情報を入れる目的は、翻訳された外国語の示す動画の位置を分かりやすくするためです。</td><td>AppleScriptクイックアクション</td></tr><tr><td><a href="https://imakat.com/wp-content/pmedia/scripts_pub/字幕文章の時刻を削除するworkflow_pub.txt" target="_blank">字幕文章の時刻を削除する</a>(timeremoved.vtt)</td><td>上の「vttからタイムありvttを生成する」を取り消しにします。※元のファイル_timeadded.vttは残します。新しいファイルは、_timeadded_timeremoved .vttになります。</td><td>AppleScriptクイックアクション</td></tr><tr><td><a href="https://imakat.com/wp-content/pmedia/scripts_pub/vttから字幕一覧用txtを生成workflow_pub.txt" target="_blank">字幕vttから字幕一覧txtを生成する</a></td><td>動画内の字幕は、多くの場合、翻訳されないか動作不安定になるが、字幕一覧txtが動画の下にあれば、翻訳が可能になります。タイムコードの位置にジャンプする_listJ.txtおよびジャンプしない_list.txtを生成します。※元のファイルは残します。新しいファイルは、_list.txtになります。なお既存の場合は上書きします。</td><td>AppleScriptクイックアクション</td></tr><tr><td><a href="#link2"><s>vttから字幕一覧用txtを生成する</s></a></td><td><a href="https://imakat.com/ds5?drid=5">動画(無音)</a><br>字幕一覧用txtは、動画の外側に配置するので、Google翻訳される。<strong>→「字幕vttから字幕一覧txtを生成する」へ集約</strong>。</td><td>AppleScriptクイックアクション</td></tr><tr><td><a href="https://imakat.com/2024/01/22/18942#link1"><s>DropboxリンクをWordPress用に変換する(クリップボード保存)</s></a></td><td><a href="https://imakat.com/ds5?drid=6">動画(無音)</a><br>Dropboxのメディアを外部リンクするために、URLに指定された変更を加える。URLの記述の中の &#8220;www.dropbox.com&#8221;は、そのまま使うようにとりあえず判断した。</td><td>AppleScriptクイックアクション</td></tr><tr><td><a href="https://imakat.com/2024/01/22/18942/#link2"><s>★WordPress動画貼り付けコード生成(クリップボード保存)</s></a></td><td><a href="https://imakat.com/ds5?drid=7">動画(無音)</a><br>WordPressのカスタムhtmlへ貼り付ける。初期画面用画像、動画本体、字幕のファイルを予め用意。→マイライブラリの運用に切り替え。</td><td><span class="red">AppleScriptアプリケーション</span></td></tr><tr><td><a href="#link1">★<s>字幕一覧用txtからカスタムhtmlを生成(クリップボード保存)</s></a></td><td><a href="https://imakat.com/ds5?drid=8">動画(無音</a><a href="https://imakat.com/ds?drid=8">)</a> <a href="https://imakat.com/ds5/?drid=9">動画(無音</a><a href="https://imakat.com/ds/?drid=9">)</a><br>字幕一覧用txtを表示するボックスレイアウトを作る。Google翻訳の表示にて改行がうまくいかない状態がある<strong>。→「字幕vttから字幕一覧txtを生成する」へ集約</strong>。</td><td>AppleScriptクイックアクション</td></tr><tr><td><a href="https://imakat.com/wp-content/pmedia/scripts_pub/字幕空白詰め_vimeo_auto_generated_captions_vttの修正_pub.txt" target="_blank">vimeo_auto_generated_captions.vttの修正をする</a></td><td>Vimeoの字幕自動生成からダウンロードしたファイルの、文字列の空白を削除する。vttのまま。※直接上書きします。何度でもダウンロードできるため。</td><td>AppleScriptクイックアクション</td></tr><tr><td><a href="https://imakat.com/2019/12/14/8731/"><s>★Vimeo登録用htmlコード生成</s></a></td><td>方法１：WordPressで、カスタムhtmlブロックへ登録して、パターンを作成しておく。<br>方法２：Stream deckなどで文字列として登録しておく。<br>方法３：Appleクイックアクションで登録しておく。など。→マイライブラリの運用に切り替え。</td><td>字幕作成とは直接に関係ありませんが、迷子にならないように。</td></tr><tr><td><a href="https://imakat.com/wp-content/pmedia/scripts_pub/字幕時刻の秒ずらしを行うworkflow_pub.txt" target="_blank">vttの字幕時刻の秒ずらしを行う。</a>(timeshifted.vtt)</td><td><a href="https://imakat.com/ds5?drid=25" target="_blank">動画(無音)</a><br>動画の途中で、挿入や削除を行うと、字幕ファイルの時刻にズレが生じます。挿入や削除を行った以降の動画をコピーして、その分の秒ずらしを行うことができます。<span class="bold-red">但し、文章の頭に時刻情報を入れてある(_timeadded.vtt)については、エラーになります。一度、上の「字幕文章の時刻を削除する」を実行した後で、ここを実行してください</span>。<br>※元のファイルを残します。新しいファイル名は&#8221;元のファイル名_timeshited.vtt&#8221;となります。</td><td>AppleScriptクイックアクション</td></tr></tbody></table></figure>



<h3 class="wp-block-heading">ポイント解説</h3>



<h4 class="wp-block-heading">１　その他</h4>



<p class="wp-block-paragraph">WindowsPCでのこのブログにリンクした動画の再生についてです。Dropboxの動画についてですが、Dropboxの動画は、ブログに貼り付けた場合は、そのまま素直にストリーミング再生されます。しかし、URLをリンクで再生しようとした場合、ダウンロードしてからの再生を求められます。それはとても面倒なこと。その点も勘案して、Google翻訳アイコン、動画、字幕一覧の各場所をテンプレートにして、動画については、Vimeo、Dropbox、WordPressサーバー、YouTubeなどを貼り付けられるようにして、WordPressの固定ページで動画ライブラリーとしてひとまとめにするように改善中です。</p>



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



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



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">20002</post-id>	</item>
	</channel>
</rss>
