[{"data":1,"prerenderedAt":1804},["ShallowReactive",2],{"blog-ja-browser-streaming-remux-mp4-isobmff":3,"blog-ja-browser-streaming-remux-mp4-isobmff-surround":1792},{"id":4,"title":5,"author":6,"body":7,"category":1777,"date":1778,"description":1779,"extension":1780,"image":1781,"lastModified":1778,"meta":1782,"navigation":242,"path":1783,"readingTime":440,"seo":1784,"stem":1785,"tags":1786,"__hash__":1791},"blog_ja\u002Fja\u002Fblog\u002Fbrowser-streaming-remux-mp4-isobmff.md","ブラウザでの動画リマックス：fMP4、ISOBMFF、トランスコードしない理由","FlowPick チーム",{"type":8,"value":9,"toc":1763},"minimark",[10,14,17,20,25,28,35,41,48,58,61,65,68,78,89,99,103,110,116,125,538,541,545,548,587,598,612,616,1146,1152,1160,1164,1167,1170,1411,1414,1417,1433,1437,1440,1468,1473,1480,1483,1486,1553,1556,1564,1567,1577,1590,1596,1610,1613,1654,1657,1664,1667,1680,1700,1703,1706,1759],[11,12,13],"p",{},"ストリーミング動画ダウンロードの最終ステップ——TS セグメントや fMP4 セグメントを再生できる MP4 にまとめる——には 2 つのやり方がある。正しい方と間違った方。",[11,15,16],{},"間違いはトランスコード：デコードして、再エンコードする。遅くて CPU 負荷が高くて品質低下があって、ブラウザではほぼ実行不可能。",[11,18,19],{},"正しいのはリマックス：コンテナをデマルチプレクスして、生の codec データを取り出し、新しいコンテナにマルチプレクスし直す。速くて CPU 負荷が低くて品質劣化がない。この記事はブラウザでリマックスをどうやるか、なぜこれができるか、多くの人がこれが正しい選択だと気づいていない理由を説明する。",[21,22,24],"h2",{"id":23},"コンテナ-vs-codec","コンテナ vs codec",[11,26,27],{},"概念を分ける。90% の混乱はここをごちゃ混ぜにすることから来る。",[11,29,30,34],{},[31,32,33],"strong",{},"Codec"," は圧縮アルゴリズム。H.264、H.265、VP9、AV1——これらは codec。画像の列をどうビットストリームに圧縮するかを定義する。",[11,36,37,40],{},[31,38,39],{},"コンテナ"," はファイルフォーマット。MP4、MKV、WebM、MPEG-TS——これらはコンテナ。codec ビットストリーム、音声トラック、字幕、タイムスタンプ等を 1 つのファイルにどうパッケージするかを定義する。",[11,42,43,47],{},[44,45,46],"code",{},".mp4"," ファイルには H.264 映像 + AAC 音声が入っているかもしれないし、H.265 映像 + Opus 音声が入っているかもしれない。コンテナは codec が何かを気にしない——パッケージするだけ。",[11,49,50,53,54,57],{},[31,51,52],{},"トランスコード"," は codec を変える（H.264 → H.265）。",[31,55,56],{},"リマックス"," はコンテナを変える（TS → MP4）が codec は維持する。トランスコードはデコードして再エンコードする。リマックスはデコードしない——codec データをそのまま移す。",[11,59,60],{},"HLS ダウンロードのシナリオ：ソースは TS セグメント、H.264\u002FAAC が入っている。やるべきは TS → MP4 リマックス。H.264 と AAC は手付かずでそのまま移る。だから 30 分の動画が 10 秒で終わる——デコードしないので計算量がない。",[21,62,64],{"id":63},"isobmffmp4-の構造","ISOBMFF：MP4 の構造",[11,66,67],{},"MP4 は ISO Base Media File Format（ISOBMFF、ISO 14496-12）のインスタンス。構造はボックス（または atom）の再帰的ネスト。",[69,70,75],"pre",{"className":71,"code":73,"language":74},[72],"language-text","ftyp    ファイルタイプボックス\nmoov    ムービーボックス（メタデータ）\n├── mvhd    ムービーヘッダー（長さ、timescale）\n├── trak    トラック 1（映像）\n│   ├── tkhd    トラックヘッダー\n│   ├── mdia    メディア情報\n│   │   ├── mdhd    メディアヘッダー（codec の timescale）\n│   │   ├── hdlr    ハンドラー（'vide' = 映像）\n│   │   └── minf    メディア情報\n│   │       ├── stbl   サンプルテーブル（重要！）\n│   │       │   ├── stsd   サンプル記述（codec 詳細）\n│   │       │   ├── stts   デコード時間→表示時間\n│   │       │   ├── stsc   サンプル→チャンク\n│   │       │   ├── stsz   サンプルサイズ\n│   │       │   └── stco   チャンクオフセット\n│   │       └── ...\n│   └── ...\n├── trak    トラック 2（音声）\n│   └── ...\n└── ...\nmdat    メディアデータ（実際の codec ビットストリーム）\n","text",[44,76,73],{"__ignoreMap":77},"",[11,79,80,81,84,85,88],{},"重要なのは ",[44,82,83],{},"stbl","（サンプルテーブル）——プレイヤーに「mdat のどのバイトがどのフレームか、各フレームのサイズ、いつ表示するか」を教える。このテーブルがないと、",[44,86,87],{},"mdat"," は意味不明なバイトの塊。",[11,90,91,92,95,96,98],{},"リマックスがやるべきこと：正しい ",[44,93,94],{},"moov"," を構築し、",[44,97,87],{}," に codec データをそのまま入れる。",[21,100,102],{"id":101},"なぜ-fmp4-は結合できるのか","なぜ fMP4 は結合できるのか",[11,104,105,106,109],{},"Fragmented MP4（fMP4）は ISOBMFF のバリアントで、各トラックが ",[44,107,108],{},"moof","（ムービーフラグメント）ボックスを持つ複数のフラグメントに分割される。",[69,111,114],{"className":112,"code":113,"language":74},[72],"ftyp\nmoov        （トラックレイアウト、でもサンプルテーブルはない）\nmoof        （フラグメント 1 のサンプルテーブル）\nmdat        （フラグメント 1 のメディアデータ）\nmoof        （フラグメント 2 のサンプルテーブル）\nmdat        （フラグメント 2 のメディアデータ）\n...\n",[44,115,113],{"__ignoreMap":77},[11,117,118,119,121,122,124],{},"各 ",[44,120,108],{},"+",[44,123,87],{}," ペアは自己記述的——サンプルテーブルとメディアデータを自分で持つ。だから fMP4 フラグメントの結合はバイトレベルの連結：",[69,126,130],{"className":127,"code":128,"language":129,"meta":77,"style":77},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","async function mergeFmp4(initUrl, segmentUrls) {\n  const init = new Uint8Array(await (await fetch(initUrl)).arrayBuffer())\n  const chunks = [init]\n\n  for (const url of segmentUrls) {\n    chunks.push(new Uint8Array(await (await fetch(url)).arrayBuffer()))\n  }\n\n  const total = chunks.reduce((s, c) => s + c.byteLength, 0)\n  const out = new Uint8Array(total)\n  let offset = 0\n\n  for (const chunk of chunks) {\n    out.set(chunk, offset)\n    offset += chunk.byteLength\n  }\n\n  return new Blob([out], { type: 'video\u002Fmp4' })\n}\n","js",[44,131,132,168,218,237,244,269,311,317,322,378,399,413,418,438,460,476,481,486,532],{"__ignoreMap":77},[133,134,137,141,144,148,152,156,159,162,165],"span",{"class":135,"line":136},"line",1,[133,138,140],{"class":139},"spNyl","async",[133,142,143],{"class":139}," function",[133,145,147],{"class":146},"s2Zo4"," mergeFmp4",[133,149,151],{"class":150},"sMK4o","(",[133,153,155],{"class":154},"sHdIc","initUrl",[133,157,158],{"class":150},",",[133,160,161],{"class":154}," segmentUrls",[133,163,164],{"class":150},")",[133,166,167],{"class":150}," {\n",[133,169,171,174,178,181,184,187,190,194,197,199,202,204,206,209,212,215],{"class":135,"line":170},2,[133,172,173],{"class":139},"  const",[133,175,177],{"class":176},"sTEyZ"," init",[133,179,180],{"class":150}," =",[133,182,183],{"class":150}," new",[133,185,186],{"class":146}," Uint8Array",[133,188,151],{"class":189},"swJcz",[133,191,193],{"class":192},"s7zQu","await",[133,195,196],{"class":189}," (",[133,198,193],{"class":192},[133,200,201],{"class":146}," fetch",[133,203,151],{"class":189},[133,205,155],{"class":176},[133,207,208],{"class":189},"))",[133,210,211],{"class":150},".",[133,213,214],{"class":146},"arrayBuffer",[133,216,217],{"class":189},"())\n",[133,219,221,223,226,228,231,234],{"class":135,"line":220},3,[133,222,173],{"class":139},[133,224,225],{"class":176}," chunks",[133,227,180],{"class":150},[133,229,230],{"class":189}," [",[133,232,233],{"class":176},"init",[133,235,236],{"class":189},"]\n",[133,238,240],{"class":135,"line":239},4,[133,241,243],{"emptyLinePlaceholder":242},true,"\n",[133,245,247,250,252,255,258,261,263,266],{"class":135,"line":246},5,[133,248,249],{"class":192},"  for",[133,251,196],{"class":189},[133,253,254],{"class":139},"const",[133,256,257],{"class":176}," url",[133,259,260],{"class":150}," of",[133,262,161],{"class":176},[133,264,265],{"class":189},") ",[133,267,268],{"class":150},"{\n",[133,270,272,275,277,280,282,285,287,289,291,293,295,297,299,302,304,306,308],{"class":135,"line":271},6,[133,273,274],{"class":176},"    chunks",[133,276,211],{"class":150},[133,278,279],{"class":146},"push",[133,281,151],{"class":189},[133,283,284],{"class":150},"new",[133,286,186],{"class":146},[133,288,151],{"class":189},[133,290,193],{"class":192},[133,292,196],{"class":189},[133,294,193],{"class":192},[133,296,201],{"class":146},[133,298,151],{"class":189},[133,300,301],{"class":176},"url",[133,303,208],{"class":189},[133,305,211],{"class":150},[133,307,214],{"class":146},[133,309,310],{"class":189},"()))\n",[133,312,314],{"class":135,"line":313},7,[133,315,316],{"class":150},"  }\n",[133,318,320],{"class":135,"line":319},8,[133,321,243],{"emptyLinePlaceholder":242},[133,323,325,327,330,332,334,336,339,341,343,346,348,351,353,356,359,362,364,366,369,371,375],{"class":135,"line":324},9,[133,326,173],{"class":139},[133,328,329],{"class":176}," total",[133,331,180],{"class":150},[133,333,225],{"class":176},[133,335,211],{"class":150},[133,337,338],{"class":146},"reduce",[133,340,151],{"class":189},[133,342,151],{"class":150},[133,344,345],{"class":154},"s",[133,347,158],{"class":150},[133,349,350],{"class":154}," c",[133,352,164],{"class":150},[133,354,355],{"class":139}," =>",[133,357,358],{"class":176}," s",[133,360,361],{"class":150}," +",[133,363,350],{"class":176},[133,365,211],{"class":150},[133,367,368],{"class":176},"byteLength",[133,370,158],{"class":150},[133,372,374],{"class":373},"sbssI"," 0",[133,376,377],{"class":189},")\n",[133,379,381,383,386,388,390,392,394,397],{"class":135,"line":380},10,[133,382,173],{"class":139},[133,384,385],{"class":176}," out",[133,387,180],{"class":150},[133,389,183],{"class":150},[133,391,186],{"class":146},[133,393,151],{"class":189},[133,395,396],{"class":176},"total",[133,398,377],{"class":189},[133,400,402,405,408,410],{"class":135,"line":401},11,[133,403,404],{"class":139},"  let",[133,406,407],{"class":176}," offset",[133,409,180],{"class":150},[133,411,412],{"class":373}," 0\n",[133,414,416],{"class":135,"line":415},12,[133,417,243],{"emptyLinePlaceholder":242},[133,419,421,423,425,427,430,432,434,436],{"class":135,"line":420},13,[133,422,249],{"class":192},[133,424,196],{"class":189},[133,426,254],{"class":139},[133,428,429],{"class":176}," chunk",[133,431,260],{"class":150},[133,433,225],{"class":176},[133,435,265],{"class":189},[133,437,268],{"class":150},[133,439,441,444,446,449,451,454,456,458],{"class":135,"line":440},14,[133,442,443],{"class":176},"    out",[133,445,211],{"class":150},[133,447,448],{"class":146},"set",[133,450,151],{"class":189},[133,452,453],{"class":176},"chunk",[133,455,158],{"class":150},[133,457,407],{"class":176},[133,459,377],{"class":189},[133,461,463,466,469,471,473],{"class":135,"line":462},15,[133,464,465],{"class":176},"    offset",[133,467,468],{"class":150}," +=",[133,470,429],{"class":176},[133,472,211],{"class":150},[133,474,475],{"class":176},"byteLength\n",[133,477,479],{"class":135,"line":478},16,[133,480,316],{"class":150},[133,482,484],{"class":135,"line":483},17,[133,485,243],{"emptyLinePlaceholder":242},[133,487,489,492,494,497,500,503,506,508,511,514,517,520,524,527,530],{"class":135,"line":488},18,[133,490,491],{"class":192},"  return",[133,493,183],{"class":150},[133,495,496],{"class":146}," Blob",[133,498,499],{"class":189},"([",[133,501,502],{"class":176},"out",[133,504,505],{"class":189},"]",[133,507,158],{"class":150},[133,509,510],{"class":150}," {",[133,512,513],{"class":189}," type",[133,515,516],{"class":150},":",[133,518,519],{"class":150}," '",[133,521,523],{"class":522},"sfazB","video\u002Fmp4",[133,525,526],{"class":150},"'",[133,528,529],{"class":150}," }",[133,531,377],{"class":189},[133,533,535],{"class":135,"line":534},19,[133,536,537],{"class":150},"}\n",[11,539,540],{},"これが FlowPick の DASH ダウンロードパスのコアロジック——約 20 行の JS。計算することは何もないので、バイト連結だけでネイティブツールと同じくらい速い。",[21,542,544],{"id":543},"なぜ-ts-はこうはいかないのか","なぜ TS はこうはいかないのか",[11,546,547],{},"MPEG-TS には「各セグメントが独立して結合可能」というプロパティがない。TS セグメントは 188 バイトのパケットのストリームで、各パケットに PID、PTS、DTS がある。複数の TS セグメントを 1 つの MP4 にまとめるには：",[549,550,551,555,558,561,566,572,578],"ol",{},[552,553,554],"li",{},"TS パケットヘッダーを解析し、PID で映像\u002F音声に分離",[552,556,557],{},"PES パケットから codec データを抽出",[552,559,560],{},"各フレームの PTS\u002FDTS とサイズを記録",[552,562,563,565],{},[44,564,83],{}," ボックス（サンプルテーブル）を構築",[552,567,568,569,571],{},"完全な ",[44,570,94],{}," を構築",[552,573,574,575,577],{},"映像\u002F音声サンプルを再インターリーブして ",[44,576,87],{}," に書き込む",[552,579,580,583,584,586],{},[44,581,582],{},"stco","（チャンクオフセット）をバックパッチ——",[44,585,94],{}," を書く前に最終オフセットが分からないので",[11,588,589,590,593,594,597],{},"これは重い作業だが、デコードは不要。FFmpeg の ",[44,591,592],{},"mpegts"," デマルチプレクサ + ",[44,595,596],{},"mp4"," マルチプレクサがこれを行う。ブラウザでは FFmpeg WASM が最も一般的な選択肢——FFmpeg がこのロジックを実装していて、エッジケースをすべて処理しているから。",[11,599,600,603,604,611],{},[31,601,602],{},"純 JS のパスもある。"," ",[605,606,610],"a",{"href":607,"rel":608},"https:\u002F\u002Fgithub.com\u002FitsKaynine\u002Fjayer",[609],"nofollow","jayer"," のようなライブラリは TS デマルチプレクスを行い、手動で MP4 ボックスを構築できる。利点は 25MB の WASM ダウンロードが不要なこと。欠点はエッジケースでコケる可能性——FFmpeg は何十年も様々な codec と TS バリアントでテストされている。",[21,613,615],{"id":614},"ffmpeg-wasm-でリマックス","FFmpeg WASM でリマックス",[69,617,619],{"className":127,"code":618,"language":129,"meta":77,"style":77},"import { FFmpeg } from '@ffmpeg\u002Fffmpeg'\nimport { fetchFile } from '@ffmpeg\u002Futil'\n\nconst ffmpeg = new FFmpeg()\nawait ffmpeg.load({\n  coreURL: '\u002Fffmpeg-core.js',\n  wasmURL: '\u002Fffmpeg-core.wasm'\n})\n\n\u002F\u002F すべての TS セグメントをメモリ FS に書き込む\nawait ffmpeg.writeFile('list.txt', segmentUrls.map((_, i) => `file 'seg_${i}.ts'`).join('\\n'))\nfor (let i = 0; i \u003C segmentUrls.length; i++) {\n  await ffmpeg.writeFile(`seg_${i}.ts`, await fetchFile(segmentUrls[i]))\n}\n\n\u002F\u002F リマックス\nawait ffmpeg.exec([\n  '-f', 'concat',\n  '-i', 'list.txt',\n  '-c', 'copy',         \u002F\u002F トランスコードしない！リマックスだけ\n  '-f', 'mp4',\n  '-movflags', '+faststart',\n  'out.mp4'\n])\n\nconst data = await ffmpeg.readFile('out.mp4')\nconst blob = new Blob([data.buffer], { type: 'video\u002Fmp4' })\n",[44,620,621,644,664,668,685,701,718,732,739,743,749,833,876,925,929,933,938,952,973,992,1016,1035,1056,1066,1072,1077,1106],{"__ignoreMap":77},[133,622,623,626,628,631,633,636,638,641],{"class":135,"line":136},[133,624,625],{"class":192},"import",[133,627,510],{"class":150},[133,629,630],{"class":176}," FFmpeg",[133,632,529],{"class":150},[133,634,635],{"class":192}," from",[133,637,519],{"class":150},[133,639,640],{"class":522},"@ffmpeg\u002Fffmpeg",[133,642,643],{"class":150},"'\n",[133,645,646,648,650,653,655,657,659,662],{"class":135,"line":170},[133,647,625],{"class":192},[133,649,510],{"class":150},[133,651,652],{"class":176}," fetchFile",[133,654,529],{"class":150},[133,656,635],{"class":192},[133,658,519],{"class":150},[133,660,661],{"class":522},"@ffmpeg\u002Futil",[133,663,643],{"class":150},[133,665,666],{"class":135,"line":220},[133,667,243],{"emptyLinePlaceholder":242},[133,669,670,672,675,678,680,682],{"class":135,"line":239},[133,671,254],{"class":139},[133,673,674],{"class":176}," ffmpeg ",[133,676,677],{"class":150},"=",[133,679,183],{"class":150},[133,681,630],{"class":146},[133,683,684],{"class":176},"()\n",[133,686,687,689,692,694,697,699],{"class":135,"line":246},[133,688,193],{"class":192},[133,690,691],{"class":176}," ffmpeg",[133,693,211],{"class":150},[133,695,696],{"class":146},"load",[133,698,151],{"class":176},[133,700,268],{"class":150},[133,702,703,706,708,710,713,715],{"class":135,"line":271},[133,704,705],{"class":189},"  coreURL",[133,707,516],{"class":150},[133,709,519],{"class":150},[133,711,712],{"class":522},"\u002Fffmpeg-core.js",[133,714,526],{"class":150},[133,716,717],{"class":150},",\n",[133,719,720,723,725,727,730],{"class":135,"line":313},[133,721,722],{"class":189},"  wasmURL",[133,724,516],{"class":150},[133,726,519],{"class":150},[133,728,729],{"class":522},"\u002Fffmpeg-core.wasm",[133,731,643],{"class":150},[133,733,734,737],{"class":135,"line":319},[133,735,736],{"class":150},"}",[133,738,377],{"class":176},[133,740,741],{"class":135,"line":324},[133,742,243],{"emptyLinePlaceholder":242},[133,744,745],{"class":135,"line":380},[133,746,748],{"class":747},"sHwdD","\u002F\u002F すべての TS セグメントをメモリ FS に書き込む\n",[133,750,751,753,755,757,760,762,764,767,769,771,773,775,778,780,782,785,787,790,792,794,797,800,803,806,808,811,814,816,818,821,823,825,828,830],{"class":135,"line":401},[133,752,193],{"class":192},[133,754,691],{"class":176},[133,756,211],{"class":150},[133,758,759],{"class":146},"writeFile",[133,761,151],{"class":176},[133,763,526],{"class":150},[133,765,766],{"class":522},"list.txt",[133,768,526],{"class":150},[133,770,158],{"class":150},[133,772,161],{"class":176},[133,774,211],{"class":150},[133,776,777],{"class":146},"map",[133,779,151],{"class":176},[133,781,151],{"class":150},[133,783,784],{"class":154},"_",[133,786,158],{"class":150},[133,788,789],{"class":154}," i",[133,791,164],{"class":150},[133,793,355],{"class":139},[133,795,796],{"class":150}," `",[133,798,799],{"class":522},"file 'seg_",[133,801,802],{"class":150},"${",[133,804,805],{"class":176},"i",[133,807,736],{"class":150},[133,809,810],{"class":522},".ts'",[133,812,813],{"class":150},"`",[133,815,164],{"class":176},[133,817,211],{"class":150},[133,819,820],{"class":146},"join",[133,822,151],{"class":176},[133,824,526],{"class":150},[133,826,827],{"class":176},"\\n",[133,829,526],{"class":150},[133,831,832],{"class":176},"))\n",[133,834,835,838,840,843,846,848,850,853,855,858,860,862,865,867,869,872,874],{"class":135,"line":415},[133,836,837],{"class":192},"for",[133,839,196],{"class":176},[133,841,842],{"class":139},"let",[133,844,845],{"class":176}," i ",[133,847,677],{"class":150},[133,849,374],{"class":373},[133,851,852],{"class":150},";",[133,854,845],{"class":176},[133,856,857],{"class":150},"\u003C",[133,859,161],{"class":176},[133,861,211],{"class":150},[133,863,864],{"class":176},"length",[133,866,852],{"class":150},[133,868,789],{"class":176},[133,870,871],{"class":150},"++",[133,873,265],{"class":176},[133,875,268],{"class":150},[133,877,878,881,883,885,887,889,891,894,896,898,900,903,905,907,910,912,914,917,920,922],{"class":135,"line":420},[133,879,880],{"class":192},"  await",[133,882,691],{"class":176},[133,884,211],{"class":150},[133,886,759],{"class":146},[133,888,151],{"class":189},[133,890,813],{"class":150},[133,892,893],{"class":522},"seg_",[133,895,802],{"class":150},[133,897,805],{"class":176},[133,899,736],{"class":150},[133,901,902],{"class":522},".ts",[133,904,813],{"class":150},[133,906,158],{"class":150},[133,908,909],{"class":192}," await",[133,911,652],{"class":146},[133,913,151],{"class":189},[133,915,916],{"class":176},"segmentUrls",[133,918,919],{"class":189},"[",[133,921,805],{"class":176},[133,923,924],{"class":189},"]))\n",[133,926,927],{"class":135,"line":440},[133,928,537],{"class":150},[133,930,931],{"class":135,"line":462},[133,932,243],{"emptyLinePlaceholder":242},[133,934,935],{"class":135,"line":478},[133,936,937],{"class":747},"\u002F\u002F リマックス\n",[133,939,940,942,944,946,949],{"class":135,"line":483},[133,941,193],{"class":192},[133,943,691],{"class":176},[133,945,211],{"class":150},[133,947,948],{"class":146},"exec",[133,950,951],{"class":176},"([\n",[133,953,954,957,960,962,964,966,969,971],{"class":135,"line":488},[133,955,956],{"class":150},"  '",[133,958,959],{"class":522},"-f",[133,961,526],{"class":150},[133,963,158],{"class":150},[133,965,519],{"class":150},[133,967,968],{"class":522},"concat",[133,970,526],{"class":150},[133,972,717],{"class":150},[133,974,975,977,980,982,984,986,988,990],{"class":135,"line":534},[133,976,956],{"class":150},[133,978,979],{"class":522},"-i",[133,981,526],{"class":150},[133,983,158],{"class":150},[133,985,519],{"class":150},[133,987,766],{"class":522},[133,989,526],{"class":150},[133,991,717],{"class":150},[133,993,995,997,1000,1002,1004,1006,1009,1011,1013],{"class":135,"line":994},20,[133,996,956],{"class":150},[133,998,999],{"class":522},"-c",[133,1001,526],{"class":150},[133,1003,158],{"class":150},[133,1005,519],{"class":150},[133,1007,1008],{"class":522},"copy",[133,1010,526],{"class":150},[133,1012,158],{"class":150},[133,1014,1015],{"class":747},"         \u002F\u002F トランスコードしない！リマックスだけ\n",[133,1017,1019,1021,1023,1025,1027,1029,1031,1033],{"class":135,"line":1018},21,[133,1020,956],{"class":150},[133,1022,959],{"class":522},[133,1024,526],{"class":150},[133,1026,158],{"class":150},[133,1028,519],{"class":150},[133,1030,596],{"class":522},[133,1032,526],{"class":150},[133,1034,717],{"class":150},[133,1036,1038,1040,1043,1045,1047,1049,1052,1054],{"class":135,"line":1037},22,[133,1039,956],{"class":150},[133,1041,1042],{"class":522},"-movflags",[133,1044,526],{"class":150},[133,1046,158],{"class":150},[133,1048,519],{"class":150},[133,1050,1051],{"class":522},"+faststart",[133,1053,526],{"class":150},[133,1055,717],{"class":150},[133,1057,1059,1061,1064],{"class":135,"line":1058},23,[133,1060,956],{"class":150},[133,1062,1063],{"class":522},"out.mp4",[133,1065,643],{"class":150},[133,1067,1069],{"class":135,"line":1068},24,[133,1070,1071],{"class":176},"])\n",[133,1073,1075],{"class":135,"line":1074},25,[133,1076,243],{"emptyLinePlaceholder":242},[133,1078,1080,1082,1085,1087,1089,1091,1093,1096,1098,1100,1102,1104],{"class":135,"line":1079},26,[133,1081,254],{"class":139},[133,1083,1084],{"class":176}," data ",[133,1086,677],{"class":150},[133,1088,909],{"class":192},[133,1090,691],{"class":176},[133,1092,211],{"class":150},[133,1094,1095],{"class":146},"readFile",[133,1097,151],{"class":176},[133,1099,526],{"class":150},[133,1101,1063],{"class":522},[133,1103,526],{"class":150},[133,1105,377],{"class":176},[133,1107,1109,1111,1114,1116,1118,1120,1123,1125,1128,1130,1132,1134,1136,1138,1140,1142,1144],{"class":135,"line":1108},27,[133,1110,254],{"class":139},[133,1112,1113],{"class":176}," blob ",[133,1115,677],{"class":150},[133,1117,183],{"class":150},[133,1119,496],{"class":146},[133,1121,1122],{"class":176},"([data",[133,1124,211],{"class":150},[133,1126,1127],{"class":176},"buffer]",[133,1129,158],{"class":150},[133,1131,510],{"class":150},[133,1133,513],{"class":189},[133,1135,516],{"class":150},[133,1137,519],{"class":150},[133,1139,523],{"class":522},[133,1141,526],{"class":150},[133,1143,529],{"class":150},[133,1145,377],{"class":176},[11,1147,1148,1151],{},[44,1149,1150],{},"-c copy"," がここの魔法。FFmpeg に「デコードしない、再エンコードしない、codec データをそのまま移す」と伝える。これがリマックス。",[11,1153,1154,1156,1157,1159],{},[44,1155,1051],{}," は ",[44,1158,94],{}," ボックスをファイル先頭に移動する——プレイヤーがファイルの最初の数 KB を取得するだけで再生を開始でき、ファイル全体をダウンロードしなくてよい。ストリーミングとプログレッシブダウンロードには必須。",[21,1161,1163],{"id":1162},"メモリ管理大きなファイルのチャンク処理","メモリ管理：大きなファイルのチャンク処理",[11,1165,1166],{},"上記は 30 分の 1080p では問題ない——500MB のセグメント + 出力はブラウザタブの 2GB ソフト上限に収まる。4K 30 分は 2.5GB、4K 2 時間の映画は 10GB+。",[11,1168,1169],{},"チャンク戦略：",[69,1171,1173],{"className":127,"code":1172,"language":129,"meta":77,"style":77},"async function chunkedRemux(segmentUrls, chunkSize = 50) {\n  const parts = []\n\n  for (let i = 0; i \u003C segmentUrls.length; i += chunkSize) {\n    const chunk = segmentUrls.slice(i, i + chunkSize)\n    const partBlob = await remuxChunk(chunk, i === 0)\n    parts.push(partBlob)\n    \u002F\u002F メモリを解放\n    await new Promise(r => setTimeout(r, 0))\n  }\n\n  return new Blob(parts, { type: 'video\u002Fmp4' })\n}\n",[44,1174,1175,1202,1214,1218,1257,1287,1316,1332,1337,1368,1372,1376,1407],{"__ignoreMap":77},[133,1176,1177,1179,1181,1184,1186,1188,1190,1193,1195,1198,1200],{"class":135,"line":136},[133,1178,140],{"class":139},[133,1180,143],{"class":139},[133,1182,1183],{"class":146}," chunkedRemux",[133,1185,151],{"class":150},[133,1187,916],{"class":154},[133,1189,158],{"class":150},[133,1191,1192],{"class":154}," chunkSize",[133,1194,180],{"class":150},[133,1196,1197],{"class":373}," 50",[133,1199,164],{"class":150},[133,1201,167],{"class":150},[133,1203,1204,1206,1209,1211],{"class":135,"line":170},[133,1205,173],{"class":139},[133,1207,1208],{"class":176}," parts",[133,1210,180],{"class":150},[133,1212,1213],{"class":189}," []\n",[133,1215,1216],{"class":135,"line":220},[133,1217,243],{"emptyLinePlaceholder":242},[133,1219,1220,1222,1224,1226,1228,1230,1232,1234,1236,1239,1241,1243,1245,1247,1249,1251,1253,1255],{"class":135,"line":239},[133,1221,249],{"class":192},[133,1223,196],{"class":189},[133,1225,842],{"class":139},[133,1227,789],{"class":176},[133,1229,180],{"class":150},[133,1231,374],{"class":373},[133,1233,852],{"class":150},[133,1235,789],{"class":176},[133,1237,1238],{"class":150}," \u003C",[133,1240,161],{"class":176},[133,1242,211],{"class":150},[133,1244,864],{"class":176},[133,1246,852],{"class":150},[133,1248,789],{"class":176},[133,1250,468],{"class":150},[133,1252,1192],{"class":176},[133,1254,265],{"class":189},[133,1256,268],{"class":150},[133,1258,1259,1262,1264,1266,1268,1270,1273,1275,1277,1279,1281,1283,1285],{"class":135,"line":246},[133,1260,1261],{"class":139},"    const",[133,1263,429],{"class":176},[133,1265,180],{"class":150},[133,1267,161],{"class":176},[133,1269,211],{"class":150},[133,1271,1272],{"class":146},"slice",[133,1274,151],{"class":189},[133,1276,805],{"class":176},[133,1278,158],{"class":150},[133,1280,789],{"class":176},[133,1282,361],{"class":150},[133,1284,1192],{"class":176},[133,1286,377],{"class":189},[133,1288,1289,1291,1294,1296,1298,1301,1303,1305,1307,1309,1312,1314],{"class":135,"line":271},[133,1290,1261],{"class":139},[133,1292,1293],{"class":176}," partBlob",[133,1295,180],{"class":150},[133,1297,909],{"class":192},[133,1299,1300],{"class":146}," remuxChunk",[133,1302,151],{"class":189},[133,1304,453],{"class":176},[133,1306,158],{"class":150},[133,1308,789],{"class":176},[133,1310,1311],{"class":150}," ===",[133,1313,374],{"class":373},[133,1315,377],{"class":189},[133,1317,1318,1321,1323,1325,1327,1330],{"class":135,"line":313},[133,1319,1320],{"class":176},"    parts",[133,1322,211],{"class":150},[133,1324,279],{"class":146},[133,1326,151],{"class":189},[133,1328,1329],{"class":176},"partBlob",[133,1331,377],{"class":189},[133,1333,1334],{"class":135,"line":319},[133,1335,1336],{"class":747},"    \u002F\u002F メモリを解放\n",[133,1338,1339,1342,1344,1348,1350,1353,1355,1358,1360,1362,1364,1366],{"class":135,"line":324},[133,1340,1341],{"class":192},"    await",[133,1343,183],{"class":150},[133,1345,1347],{"class":1346},"sBMFI"," Promise",[133,1349,151],{"class":189},[133,1351,1352],{"class":154},"r",[133,1354,355],{"class":139},[133,1356,1357],{"class":146}," setTimeout",[133,1359,151],{"class":189},[133,1361,1352],{"class":176},[133,1363,158],{"class":150},[133,1365,374],{"class":373},[133,1367,832],{"class":189},[133,1369,1370],{"class":135,"line":380},[133,1371,316],{"class":150},[133,1373,1374],{"class":135,"line":401},[133,1375,243],{"emptyLinePlaceholder":242},[133,1377,1378,1380,1382,1384,1386,1389,1391,1393,1395,1397,1399,1401,1403,1405],{"class":135,"line":415},[133,1379,491],{"class":192},[133,1381,183],{"class":150},[133,1383,496],{"class":146},[133,1385,151],{"class":189},[133,1387,1388],{"class":176},"parts",[133,1390,158],{"class":150},[133,1392,510],{"class":150},[133,1394,513],{"class":189},[133,1396,516],{"class":150},[133,1398,519],{"class":150},[133,1400,523],{"class":522},[133,1402,526],{"class":150},[133,1404,529],{"class":150},[133,1406,377],{"class":189},[133,1408,1409],{"class":135,"line":420},[133,1410,537],{"class":150},[11,1412,1413],{},"各チャンクを独立してリマックスし、FFmpeg WASM インスタンスをチャンク間でリセットし、メモリピークを「1 チャンクのデータ + FFmpeg 内部バッファ」——約 100MB に抑える。",[11,1415,1416],{},"問題：結果の MP4 は複数の独立したファイルを繋いだもので、シーク可能な単一 MP4 ではない。「再生\u002Fダウンロード」には十分。映像編集ソフトへのエクスポートには不十分——タイムラインが壊れる。",[11,1418,1419,1420,1423,1424,1426,1427,1429,1430,1432],{},"修正はリマックス後に ",[44,1421,1422],{},"mp4box.js"," で「fixup」——すべてのチャンクの ",[44,1425,94],{}," を読み、1 つの連続した ",[44,1428,94],{}," を合成し、",[44,1431,582],{}," オフセットを書き直す。複雑だが可能。FlowPick は 2GB を超えるファイルでこのパスを使う。",[21,1434,1436],{"id":1435},"いつ-ffmpeg-wasm-ではなく-webcodecs-を使うか","いつ FFmpeg WASM ではなく WebCodecs を使うか",[11,1438,1439],{},"FFmpeg を使わない代替パスがある：WebCodecs + 手書きのマルチプレクサ。",[1441,1442,1443,1454,1459],"ul",{},[552,1444,1445,1446,1449,1450,1453],{},"WebCodecs の ",[44,1447,1448],{},"VideoDecoder","\u002F",[44,1451,1452],{},"AudioDecoder"," でデコード",[552,1455,1456,1458],{},[44,1457,1422],{}," または手書きのマルチプレクサで MP4 を構築",[552,1460,1445,1461,1449,1464,1467],{},[44,1462,1463],{},"VideoEncoder",[44,1465,1466],{},"AudioEncoder"," で再エンコード（トランスコードする場合）",[11,1469,1470,1472],{},[31,1471,56],{}," にはデコーダは不要——トランスコードが発生しない。でも WebCodecs はここで別の用途がある：TS セグメントを解析して codec chunk を抽出する。FFmpeg WASM もこれをできるが、WebCodecs は一部の codec でハードウェアアクセラレーションが効いて速い。",[11,1474,1475,1479],{},[605,1476,1478],{"href":1477},"\u002Fja\u002Fblog\u002Fwebcodecs-web-workers-opfs-video-processing","WebCodecs の記事"," で完全な話をしている。私の見解：純粋なリマックス（TS → MP4）では FFmpeg WASM がまだ堅牢な選択。WebCodecs はフレームレベルの操作（サムネイル、フィルター、解析）が必要な時に適している。",[21,1481,1482],{"id":1482},"パフォーマンス実測",[11,1484,1485],{},"30 分 1080p HLS（300 TS セグメント、500MB）を MP4 にリマックス：",[1487,1488,1489,1505],"table",{},[1490,1491,1492],"thead",{},[1493,1494,1495,1499,1502],"tr",{},[1496,1497,1498],"th",{},"手段",[1496,1500,1501],{},"実測時間",[1496,1503,1504],{},"ピークメモリ",[1506,1507,1508,1520,1531,1542],"tbody",{},[1493,1509,1510,1514,1517],{},[1511,1512,1513],"td",{},"ネイティブ FFmpeg",[1511,1515,1516],{},"8s",[1511,1518,1519],{},"80MB",[1493,1521,1522,1525,1528],{},[1511,1523,1524],{},"FFmpeg WASM シングルスレッド",[1511,1526,1527],{},"75s",[1511,1529,1530],{},"220MB",[1493,1532,1533,1536,1539],{},[1511,1534,1535],{},"FFmpeg WASM マルチスレッド",[1511,1537,1538],{},"22s",[1511,1540,1541],{},"280MB",[1493,1543,1544,1547,1550],{},[1511,1545,1546],{},"純 JS TS デマルチプレクス + mp4box.js",[1511,1548,1549],{},"45s",[1511,1551,1552],{},"180MB",[11,1554,1555],{},"ネイティブがベースライン。ブラウザの選択肢はすべて 3-10 倍の範囲内。マルチスレッド FFmpeg WASM はリアルタイム使用に十分——30 分動画を 22 秒で処理。",[11,1557,1558,1559,1563],{},"純 JS のパスは FFmpeg WASM シングルスレッドより速い——WASM 呼び出しオーバーヘッドがない——が、堅牢さで劣り、一部の codec バリアントでコケる。完全なベンチマークデータは ",[605,1560,1562],{"href":1561},"\u002Fja\u002Fblog\u002Fbrowser-video-processing-performance-benchmarks","パフォーマンスベンチマークの記事"," で。",[21,1565,1566],{"id":1566},"踏んだ抜け",[11,1568,1569,1572,1573,1576],{},[31,1570,1571],{},"抜け 1：PTS\u002FDTS の不一致。"," B フレームのある H.264 はデコード順（DTS）と表示順（PTS）が異なる。",[44,1574,1575],{},"stts"," ボックス（デコード時間→表示時間）がこのマッピングを記録。リマックス時に正確に記録しないと——プレイヤーがスタッタ、タイムラインがずれ、一部のプレイヤーは拒否する。",[11,1578,1579,603,1582,1585,1586,1589],{},[31,1580,1581],{},"抜け 2：編集リスト。",[44,1583,1584],{},"elst"," ボックスでコンテナレベルのトリムができる——例えば「最初の 2 秒をスキップ」。HLS 清単に ",[44,1587,1588],{},"#EXT-X-START"," タグがある場合、編集リストを構築する必要がある。忘れると、プレイヤーは 0 から再生し、広告や黒画面から始まるかもしれない。",[11,1591,1592,1595],{},[31,1593,1594],{},"抜け 3：H.265\u002FHEVC プロファイル。"," ブラウザとデバイスでサポートする HEVC プロファイルが異なる。リマックスは codec を変えないので、ソースがデバイス未サポートのプロファイルなら、出力も未サポート。これはリマックスの問題ではない——でもユーザーは「ダウンロードした動画が再生できない」と文句を言う。",[11,1597,1598,1601,1602,1605,1606,1609],{},[31,1599,1600],{},"抜け 4：AAC 編集リストの差異。"," AAC パケットには付加データ（AudioSpecificConfig）がある。",[44,1603,1604],{},"stsd"," の ",[44,1607,1608],{},"esds"," ボックスにこれを正確に含める必要がある。忘れると、プレイヤーは再生するが音声に問題が出る——よくある症状は「映像はあるのに音が出ない」。",[21,1611,1612],{"id":1612},"参考資料",[1441,1614,1615,1623,1631,1639,1646],{},[552,1616,1617,1622],{},[605,1618,1621],{"href":1619,"rel":1620},"https:\u002F\u002Fwww.iso.org\u002Fstandard\u002F79330.html",[609],"ISO\u002FIEC 14496-12 — ISOBMFF 仕様","——正式仕様、有料だが権威",[552,1624,1625,1630],{},[605,1626,1629],{"href":1627,"rel":1628},"https:\u002F\u002Fwww.ftyps.com\u002F",[609],"MP4 File Format 仕様","——さまざまな ftyp と brand のデータベース",[552,1632,1633,1638],{},[605,1634,1637],{"href":1635,"rel":1636},"https:\u002F\u002Fffmpeg.org\u002Fffmpeg-formats.html#mpegts",[609],"FFmpeg MPEG-TS ドキュメント","——TS パケット構造の理解に",[552,1640,1641,1645],{},[605,1642,1422],{"href":1643,"rel":1644},"https:\u002F\u002Fgithub.com\u002Fgpac\u002Fmp4box.js",[609],"——GPAC の JS MP4 ツール、読み書き両方できる",[552,1647,1648,1653],{},[605,1649,1652],{"href":1650,"rel":1651},"https:\u002F\u002Fgithub.com\u002Faxiomatic-systems\u002FBento4",[609],"Bento4","——オープンソースの MP4 ツール、出力の正確性検証用",[21,1655,1656],{"id":1656},"まとめ",[11,1658,1659,1660,1663],{},"ブラウザで動画をマージする正しい哲学：",[31,1661,1662],{},"リマックス、トランスコードしない。"," ダウンロードした TS\u002FfMP4 セグメントにはすでにエンコード済みの codec データが含まれている。やるべきはコンテナを変えることだけ。この操作は O(n) のバイト操作——デコードもエンコードもなく、品質劣化もなく、CPU 重作業もない。",[11,1665,1666],{},"fMP4 の場合は文字通りバイト連結。TS の場合はデマルチプレクスが必要——FFmpeg WASM が最も堅牢な実装、純 JS も可能だがエッジケースに注意。",[11,1668,1669,1670,1672,1673,1672,1676,1679],{},"パフォーマンスの差は「10 秒 vs 5 分」——もし「マージ」ステップが明らかに遅ければ、ツールがトランスコードしている印。",[44,1671,1150],{}," \u002F ",[44,1674,1675],{},"copy_audio",[44,1677,1678],{},"copy_video"," フラグが設定されているか確認。",[11,1681,1682,1683,1691,1692,1695,1696,1699],{},"次は ",[605,1684,1686,1687,1690],{"href":1685},"\u002Fja\u002Fblog\u002Fhls-m3u8-deep-dive-encryption-multitrack","HLS 清単の ",[44,1688,1689],{},"#EXT-X-KEY"," タグの深掘り"," と、なぜ ",[44,1693,1694],{},"METHOD=SAMPLE-AES"," と ",[44,1697,1698],{},"METHOD=AES-128"," は別世界——後者は処理できる、前者は触れてはいけない——かを説明。",[1701,1702],"hr",{},[21,1704,1705],{"id":1705},"おすすめ記事",[1441,1707,1708,1717,1725,1733,1742,1750],{},[552,1709,1710,1716],{},[31,1711,1712],{},[605,1713,1715],{"href":1714},"\u002Fja\u002Fblog\u002Fhow-flowpick-merges-video-segments-in-browser","FlowPick はどうやってブラウザだけで数百の動画セグメントを 1 つの MP4 にまとめるのか","——この記事のやさしい版",[552,1718,1719,1724],{},[31,1720,1721],{},[605,1722,1723],{"href":1477},"WebCodecs + Web Workers + OPFS：ブラウザ動画処理実戦","——FFmpeg WASM が適さない時のより低レベルな API",[552,1726,1727,1732],{},[31,1728,1729],{},[605,1730,1731],{"href":1685},"HLS ディープ：暗号化、マルチトラック、覚えておくべき EXT-X タグ","——リマックスする TS セグメントがどこから来るか",[552,1734,1735,1741],{},[31,1736,1737],{},[605,1738,1740],{"href":1739},"\u002Fja\u002Fblog\u002Fdash-mpd-deep-dive-segmenttemplate-contentprotection","DASH ディープ：SegmentTemplate、ContentProtection、視点切り替え MPD","——fMP4 セグメントの供給元",[552,1743,1744,1749],{},[31,1745,1746],{},[605,1747,1748],{"href":1561},"ブラウザ動画処理パフォーマンスベンチマーク","——この記事の手法の完全なパフォーマンスデータ",[552,1751,1752,1758],{},[31,1753,1754],{},[605,1755,1757],{"href":1756},"\u002Fja\u002Fblog\u002Fflowpick-vs-yt-dlp","FlowPick vs. yt-dlp","——ブラウザリマックス vs ネイティブリマックスの違い",[1760,1761,1762],"style",{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":77,"searchDepth":170,"depth":170,"links":1764},[1765,1766,1767,1768,1769,1770,1771,1772,1773,1774,1775,1776],{"id":23,"depth":170,"text":24},{"id":63,"depth":170,"text":64},{"id":101,"depth":170,"text":102},{"id":543,"depth":170,"text":544},{"id":614,"depth":170,"text":615},{"id":1162,"depth":170,"text":1163},{"id":1435,"depth":170,"text":1436},{"id":1482,"depth":170,"text":1482},{"id":1566,"depth":170,"text":1566},{"id":1612,"depth":170,"text":1612},{"id":1656,"depth":170,"text":1656},{"id":1705,"depth":170,"text":1705},"tips","2026-08-04","TS セグメントを MP4 にまとめるのはトランスコードではなくリマックス。fMP4 構造、なぜ init セグメントが命なのか、純 JS での実装、FFmpeg WASM との性能比較まで。","md","\u002Fscreenshots\u002Fformat-conversion.png",{},"\u002Fja\u002Fblog\u002Fbrowser-streaming-remux-mp4-isobmff",{"title":5,"description":1779},"ja\u002Fblog\u002Fbrowser-streaming-remux-mp4-isobmff",[1787,596,1788,1789,1790],"remux","isobmff","wasm","ディープ","GNGszTe9mJCIdMckOR-DP44_1iUIj8pjnnew1RdNVE8",[1793,1800],{"title":1794,"path":1795,"stem":1796,"description":1797,"date":1798,"category":1799,"children":-1},"Webページから画像を一括ダウンロードする方法","\u002Fja\u002Fblog\u002Fbatch-download-images-from-any-website","ja\u002Fblog\u002Fbatch-download-images-from-any-website","FlowPickの内蔵画像ダウンローダーでWebページの画像をまとめて保存。JPG・PNG・WebPなどの形式、ファイルサイズやリソースの絞り込み、一括ダウンロードの手順を解説します。","2026-07-01","tutorials",{"title":1801,"path":1561,"stem":1802,"description":1803,"date":1778,"category":1777,"children":-1},"ブラウザ動画処理パフォーマンスベンチマーク：FFmpeg WASM vs WebCodecs vs ネイティブ","ja\u002Fblog\u002Fbrowser-video-processing-performance-benchmarks","実際のハードウェアでの実際のデータ——1080p\u002F4K\u002F8K リマックスとデコードのベンチマーク。FFmpeg WASM、WebCodecs、ネイティブ FFmpeg を横断。方法論、生データ、数字が何を意味するか付き。",1787670591055]