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

<channel>
	<title>自動トランスコード | imakat.com</title>
	<atom:link href="https://imakat.com/tag/%E8%87%AA%E5%8B%95%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89/feed/" rel="self" type="application/rss+xml" />
	<link>https://imakat.com</link>
	<description>工夫と改善で人生をちょっと豊かに</description>
	<lastBuildDate>Wed, 17 Jun 2026 00:11:39 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://i0.wp.com/imakat.com/wp-content/uploads/2023/07/cropped-80d64ecd340db4e2ca3224859b04caed.png?fit=32%2C32&#038;ssl=1</url>
	<title>自動トランスコード | imakat.com</title>
	<link>https://imakat.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">160909258</site>	<item>
		<title>【脱Vimeo】Bunny CDNへ移行しました 〜ひと所に依存しない究極の自前配信システム〜</title>
		<link>https://imakat.com/2026/06/01/29030/</link>
		
		<dc:creator><![CDATA[imakat]]></dc:creator>
		<pubDate>Sun, 31 May 2026 20:50:48 +0000</pubDate>
				<category><![CDATA[WordPress]]></category>
		<category><![CDATA[デジタル]]></category>
		<category><![CDATA[ものづくり]]></category>
		<category><![CDATA[vimeo]]></category>
		<category><![CDATA[bunny]]></category>
		<category><![CDATA[cloudflare]]></category>
		<category><![CDATA[自動トランスコード]]></category>
		<guid isPermaLink="false">https://imakat.com/?p=29030</guid>

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


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

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

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

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

  <style>
            :root{ --dr5emd-max: 1920px; }
            #subtitleOverlay, #scSubtitleOverlay, .overlay-cue, .band { display: none !important; opacity: 0 !important; }
            .imk-line { display: inline-block; width: 100%; border-radius: 2px; transition: background-color 0.1s; }
            .dr5emd-sublist details > p{ height:200px; overflow:auto; background-color:#EDF7FF; padding:2px 6px; margin:0; box-shadow:3px 3px 4px black; position: relative; }
            .dr5emd-sublist details > summary{ padding:2px 6px; width:100%; background-color:#ddd; border:none; box-shadow:3px 3px 4px black; cursor:pointer; list-style:none; }
            
                .video-wrap{position:relative;width:100%;margin:0 auto}
                #myVideo{width:100%;height:auto;min-height:200px;display:block;background:#000;}</style><div class="dr5emd-container"><div class="video-wrap"><video id="myVideo" controls poster="https://imakat.com/rd.php?id=V5sZrECn.png" playsinline preload="none" style="width:100%;height:auto;">  <source src="https://imakat.com/rd.php?id=ngym69ui.mp4" type="video/mp4">  <track src="https://imakat.com/rd.php?id=jh8hMw5r.vtt" label="日本語" srclang="ja" kind="subtitles"></video></div><div class="dr5emd-sublist">Error 502: Failed to fetch text file from remote.</div></div>            <script>
            document.addEventListener("DOMContentLoaded", function(){
                var video = document.getElementById("myVideo");
                if(!video) return;

                            });
            </script>
            

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      return true;
    }

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

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



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



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



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



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



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



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



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



<p class="wp-block-paragraph">Bunny CDNの最大の魅力は、月額の最低料金がたったの1ドル（約150円）であることです。 支払いの仕組みは「デポジット制」を採用しており、自動チャージと手動チャージが選べます。感覚としては交通系ICカードのSuicaと同じです。私は手動チャージにしてあります。デポジット残高が少なくなると通知が来るはずです。 これによって、Vimeoをこのまま放置していれば月額1,200円かかるところを、Bunny CDNなら月額150〜300円程度にまで節約できそうです。小規模な個人運営にとって、このコスト差は圧倒的です。</p>



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



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



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



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



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



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



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



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



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



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



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



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



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



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



<h3 class="wp-block-heading">５　もし再生数が多過ぎたら、その動画をYouTubeへ隔離。など自由自在。</h3>



<p class="wp-block-paragraph">そんなことはないとは思いますが、もし動画に過度に人気や関心が集まり再生数が増大してしまい、Bunnyの料金負担が重いと感じるようになってしまったとします。具体的には月2$(300円)を超えるような場合です。その場合は、その動画を特定して、残念ながら広告が入るかも知れませんが、その動画をYouTubeへ隔離するようにしたいと思います。私にとっては例外的な出来事になりますが、さりとて、その動画は、公共の道具であるインターネットを通じて公開してきているわけですので、観たい聴きたいというニーズに対しては、応えていく必要はあると思います。</p>



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



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



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



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



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



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



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



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