使い方

以下の並び替えスクリプトをコピペ(改行は気にせず)でブックマーク登録して https://www.nicovideo.jp/watch/sm44957778 でそのブックマークを起動するか開発者ツールのコンソールを使う

この手のコードや手法(ブックマークレット)に関して、悪意のあるコードを潜ませたアクターが言葉巧みに他人に起動させてそのサイトのクッキーやアカウントを奪おうとすること (ソーシャルエンジニアリング)もあるので注意。

余談

ニコニコ側のエンコードのせいかこちら側でエンコードするときにビットレートが低すぎたせいかだいぶ粗くなってしまった
適当にシャッフルしたが負荷とか考えると再考の余地あり
主だった目的は削除テストだがここまでしてホモビをニコニコで見る必要があるのかは分からない。
2025-05-11にこのスクリプトを投稿して数時間後、putImageData()/getImageData()のパフォーマンスが落ちる現象が起こる。投稿時はなんの問題も無かったのでニコニコのアップデートが影響したと思われるが根本的な原因は不明。調べると元々それらの関数はパフォーマンスに良くなかったらしく、drawImage()で代用した。
それとは別に最初に投稿したスクリプトの並び替えの方法が無駄が多かったので改善。

このページの一番最後にffmpegでエンコードした際のコマンドラインがあります

2025-05-13追記: どうやらffmpegのswaprectのバグか仕様で画質がだいぶ粗くなってしまうようである。cropで50px以下を受け付けないことを考慮すると低pxだとバグるとガタイで分析?現在気が向いたときに試行錯誤中。cropとoverlayで無理に重ね合わせる試みは画質は改善したものの部分的に正確でない出力が出てしまったりする。
2025-05-20追記:クロマサブサンプリングWikipediaとやらが原因か一因で境界で滲むらしい。(アーティファクトと言うらしい)(wikipediaの画像参照)
動画編集ソフトでロスレスを出力→ffmpegやhandbrakeで色々設定弄ってエンコード→動画編集ソフトでアーティファクトがないかを確認という流れを何度も試し分かったのはyuv422(p)であれば(適切なビットレートやcrf下で)このアーティファクトは出なくなるがyuv420(p)だと恐らくどう設定しても出てしまうようである。デブロッキングフィルターを無効化してエンコードもしてみたがどうやら効果は無いようだ。
クロマサブサンプリングが4x2なら偶数の縦解像度で分割したらアーティファクトは出ないはずでは?(検証のときは元動画を縦16や32で分割している)などまだ気になる部分はあるがh264の何らかの仕様なのだろう。ニコニコがh264の4:2:0しか対応していない(というかfirefoxがh264の4:2:2や4:4:4に対応していないのが理由なのかもしれない。vp9の4:4:4であれば再生できるが)以上このアーティファクトが出ない動画をあげるのは厳しそうだ。
気が向いたときにh264の仕様を深堀りして再挑戦したりするかもしれない。
ちなみにffmpegは-pix_fmt yuv422pにしてswaprectをしてもアーティファクトは出る謎の仕様を持つようです。この手のエンコーダー特有の仕様も面倒ですね…

2025-05-23追記: YUViewのおかげで分かったがどうやら多くのブラウザや動画プレイヤーの仕様でyuv420pだとサブサンプリングされたピクセルをアップサンプリングする際Bilinear等で処理をして視覚的に滑らかにするようだ。(Chroma Interpolation)
エンコード時の処理に加え、このChroma Interpolationもこのスクリプトでアーティファクトが生まれる一因だった。計算すれば元のYUVに戻せる。
が、結局のところニコニコ側の再エンコードでアーティファクトが生まれるのは避けられないようだ。

並び替えスクリプト

javascript: 
var response = `
    var sort_array = [
        16,15,56,58,39,8,25,47,13,74,41,3,64,46,0,54,62,17,2,61,90,34,9,111,44,4,35,101,30,18,5,85,6,28,1,55,110,103,24,32,84,99,86,38,102,7,
        57,12,79,10,98,26,97,48,52,42,14,22,76,73,71,29,21,80,67,72,114,19,37,91,66,20,11,119,43,93,63,95,49,31,81,65,118,89,83,23,51,27,106,
        68,36,40,75,50,112,78,59,45,107,109,70,117,33,60,105,69,82,100,77,88,94,87,104,108,116,96,115,92,113,53
    ];

    var offscreen;
    var offscreen_context;
    var offscreen2;
    var offscreen2_context;

    self.onmessage = (e) => {
        let frame = e.data;
        if(offscreen === undefined) {
            offscreen = new OffscreenCanvas(frame.width, frame.height);
            offscreen2 = new OffscreenCanvas(frame.width, frame.height);
            offscreen_context = offscreen.getContext('2d');
            offscreen2_context = offscreen2.getContext('2d');
        }
        offscreen2_context.putImageData(frame, 0, 0);

        var height_of_a_piece = offscreen.height / 120;
        var width_of_a_piece = offscreen.width;

        let count = 0;
        for(let i = 0; i < sort_array.length; i++) {
            offscreen_context.drawImage(offscreen2, 0, sort_array[i] * height_of_a_piece, width_of_a_piece, height_of_a_piece, 0, i * height_of_a_piece, width_of_a_piece, height_of_a_piece);
        }

        postMessage(offscreen_context.getImageData(0, 0, offscreen.width, offscreen.height));
    }
`;

(()=>{
    if (!("requestVideoFrameCallback" in HTMLVideoElement.prototype)) {
        alert("Your browser does not support requestVideoFrameCallback().");
        return -1;
    }
    let the_video = document.querySelectorAll("video")[0];
    let canvas1 = document.createElement("canvas");
    canvas1.height = the_video.videoHeight, canvas1.width = the_video.videoWidth, canvas1.style = "height: 100%; margin: auto;";
    let div1 = document.createElement("div");
    div1.style = "position: absolute; width: 100%; height: 100%; z-index: 114514; text-align: center;";
    the_video.parentElement.appendChild(div1);
    div1.appendChild(canvas1);

    let canvas2 = new OffscreenCanvas(canvas1.width, canvas1.height);

    let canvas1_context = canvas1.getContext('2d');
    let canvas2_context = canvas2.getContext('2d');

    let worker = new Worker( URL.createObjectURL( new Blob([response], {type: 'application/javascript'}) ) );
    console.log(worker);
    worker.onmessage = (e) => {
        frame_sorted = e.data;
        canvas1_context.putImageData(frame_sorted, 0, 0);
    };

    const updateCanvas = (now, metadata) => {
        canvas2_context.drawImage(the_video, 0, 0);
        let frame = canvas2_context.getImageData(0, 0, canvas2.width, canvas2.height);
        worker.postMessage(frame);
        the_video.requestVideoFrameCallback(updateCanvas);
    };

    /* Initial registration of the callback to run on the first frame */
    the_video.requestVideoFrameCallback(updateCanvas);
})();

ffmpegのエンコードした際のコマンドライン(pwsh)

./ffmpeg -i 4.mp4 -vcodec h264_nvenc -b:v 600k -vf "swaprect=640:4:0:0:0:192,swaprect=640:4:0:4:0:456,swaprect=640:4:0:8:0:92,swaprect=640:4:0:12:0:40,swaprect=640:4:0:16:0:204, `
swaprect=640:4:0:20:0:144,swaprect=640:4:0:24:0:140,swaprect=640:4:0:28:0:200,swaprect=640:4:0:32:0:80,swaprect=640:4:0:36:0:356, `
swaprect=640:4:0:40:0:196,swaprect=640:4:0:44:0:164,swaprect=640:4:0:48:0:300,swaprect=640:4:0:52:0:284,swaprect=640:4:0:56:0:124, `
swaprect=640:4:0:60:0:264,swaprect=640:4:0:64:0:476,swaprect=640:4:0:68:0:68,swaprect=640:4:0:72:0:8,swaprect=640:4:0:76:0:108, `
swaprect=640:4:0:80:0:52,swaprect=640:4:0:84:0:236,swaprect=640:4:0:88:0:148,swaprect=640:4:0:92:0:340,swaprect=640:4:0:96:0:160, `
swaprect=640:4:0:100:0:104,swaprect=640:4:0:104:0:16,swaprect=640:4:0:108:0:348,swaprect=640:4:0:112:0:448,swaprect=640:4:0:116:0:72, `
swaprect=640:4:0:120:0:376,swaprect=640:4:0:124:0:316,swaprect=640:4:0:128:0:168,swaprect=640:4:0:132:0:112,swaprect=640:4:0:136:0:464, `
swaprect=640:4:0:140:0:100,swaprect=640:4:0:144:0:360,swaprect=640:4:0:148:0:272,swaprect=640:4:0:152:0:336,swaprect=640:4:0:156:0:128, `
swaprect=640:4:0:160:0:364,swaprect=640:4:0:164:0:12,swaprect=640:4:0:168:0:220,swaprect=640:4:0:172:0:152,swaprect=640:4:0:176:0:276, `
swaprect=640:4:0:180:0:392,swaprect=640:4:0:184:0:240,swaprect=640:4:0:188:0:48,swaprect=640:4:0:192:0:212,swaprect=640:4:0:196:0:312, `
swaprect=640:4:0:200:0:372,swaprect=640:4:0:204:0:344,swaprect=640:4:0:208:0:280,swaprect=640:4:0:212:0:64,swaprect=640:4:0:216:0:208, `
swaprect=640:4:0:220:0:24,swaprect=640:4:0:224:0:56,swaprect=640:4:0:228:0:88,swaprect=640:4:0:232:0:308,swaprect=640:4:0:236:0:384, `
swaprect=640:4:0:240:0:412,swaprect=640:4:0:244:0:444,swaprect=640:4:0:248:0:292,swaprect=640:4:0:252:0:452,swaprect=640:4:0:256:0:368, `
swaprect=640:4:0:260:0:352,swaprect=640:4:0:264:0:216,swaprect=640:4:0:268:0:404,swaprect=640:4:0:272:0:36,swaprect=640:4:0:276:0:420, `
swaprect=640:4:0:280:0:400,swaprect=640:4:0:284:0:184,swaprect=640:4:0:288:0:396,swaprect=640:4:0:292:0:84,swaprect=640:4:0:296:0:332, `
swaprect=640:4:0:300:0:256,swaprect=640:4:0:304:0:432,swaprect=640:4:0:308:0:304,swaprect=640:4:0:312:0:380,swaprect=640:4:0:316:0:0, `
swaprect=640:4:0:320:0:472,swaprect=640:4:0:324:0:328,swaprect=640:4:0:328:0:424,swaprect=640:4:0:332:0:172,swaprect=640:4:0:336:0:96, `
swaprect=640:4:0:340:0:224,swaprect=640:4:0:344:0:156,swaprect=640:4:0:348:0:244,swaprect=640:4:0:352:0:436,swaprect=640:4:0:356:0:296, `
swaprect=640:4:0:360:0:32,swaprect=640:4:0:364:0:176,swaprect=640:4:0:368:0:468,swaprect=640:4:0:372:0:188,swaprect=640:4:0:376:0:440, `
swaprect=640:4:0:380:0:232,swaprect=640:4:0:384:0:460,swaprect=640:4:0:388:0:428,swaprect=640:4:0:392:0:28,swaprect=640:4:0:396:0:44, `
swaprect=640:4:0:400:0:388,swaprect=640:4:0:404:0:76,swaprect=640:4:0:408:0:416,swaprect=640:4:0:412:0:228,swaprect=640:4:0:416:0:132, `
swaprect=640:4:0:420:0:408,swaprect=640:4:0:424:0:260,swaprect=640:4:0:428:0:180,swaprect=640:4:0:432:0:252,swaprect=640:4:0:436:0:288, `
swaprect=640:4:0:440:0:20,swaprect=640:4:0:444:0:116,swaprect=640:4:0:448:0:120,swaprect=640:4:0:452:0:320,swaprect=640:4:0:456:0:60, `
swaprect=640:4:0:460:0:136,swaprect=640:4:0:464:0:4,swaprect=640:4:0:468:0:268,swaprect=640:4:0:472:0:324,swaprect=640:4:0:476:0:248" 4s.mkv
Edit

Pub: 10 May 2025 17:55 UTC

Edit: 23 May 2025 12:22 UTC

Views: 111