Downbooru

danbooruに画像をダウンロードするボタンを追加する。画像にはタグ(XP Keywords)が付与される。Picasa3と相性抜群。

Fra og med 18.05.2017. Se den nyeste version.

// ==UserScript==
// @name         Downbooru
// @name:en      Downbooru
// @namespace    https://greasyfork.org/ja/scripts/29802-downbooru
// @version      2.0.1
// @description  danbooruに画像をダウンロードするボタンを追加する。画像にはタグ(XP Keywords)が付与される。Picasa3と相性抜群。
// @description  PNGはExifが無いためJPGに変換。サイズの大きい画像は縮小される。
// @description:en download image with tags from danbooru
// @author       You
// @match        http://danbooru.donmai.us/posts/*
// @match        https://danbooru.donmai.us/posts/*
// @grant        none
// @require      http://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js
// ==/UserScript==

//参考:canvas https://blog.agektmr.com/2013/09/canvas-png-blob.html
//参考:deferred http://hamalog.tumblr.com/post/5159447047/jquerydeferredって何
(function() {
    
	getImgDataurl('large')
	.then(function(dataurl){
		//pngやあまりに大きい画像の場合dataurlの上限にひっかかるため、
		//取得できない場合jpg縮小された画像を取得
		if (dataurl.split(",")[1]) {
			setButton(dataurl);
		}
		else {
			getImgDataurl('small')
			.then(function(dataurl){
				setButton(dataurl);
			});
		}
	});

    function setButton(dataurl){
		//画像ページのID取得
        var num = /\d+/.exec($('#post-information').find('li')[0].innerText)[0];
        var file_name = "danbooru" + num + ".jpg";

        var tag_list = getTagList();
        var exif = createExif(tag_list);

        var blob = insertExif(exif, dataurl);
        var url = window.URL.createObjectURL(blob);
        var button = $('<a>', {
            href: url,
            download: file_name
        }).html('<button>Download</button>');
        var con = $('#image-container');
        con.append(button);
        return;
    }

    function getImgDataurl(size){
        var dfd = new $.Deferred();
        var img = new Image();
        switch (size) {
            case 'large':
                img.src = $('#post-options').find('a')[4].getAttribute('href');
                break;
            case 'small':
                img.src = $('#image').attr('src');
        }
        var canvas = document.createElement('canvas');
        canvas.setAttribute('width', img.width);
        canvas.setAttribute('height', img.height);
        var ctx = canvas.getContext('2d');
        img.onload = function() {
            ctx.drawImage(img, 0, 0);
            var jpg_src = canvas.toDataURL('image/jpeg');
            dfd.resolve(jpg_src);
        };
        return dfd.promise();
    }

    function getTagList() {
        var tag_list = [];
		$('.search-tag').each(function() {
			tag_list.push($(this).text().replace(/\s/g, "_"));
		});
        return tag_list;
    }

    function createExif(list) {
        var bim = "50686f746f73686f7020332e30003842494d040400000000";

        var hex_list = [];
        for (var i = 0; i < list.length; i++) {
            hex_list[i] = strToHex(list[i]);
        }

        var tags = "1c021900" + tagLength(hex_list[0]) + hex_list[0] + "1c020000020004";
        for (var i = 1; i < hex_list.length; i++) {
            tags = tags + "1c021900" + tagLength(hex_list[i]) + hex_list[i];
        }
        tags = wholeTagLength(tags) + tags;

        //XX XX [BIM] [TAGS]
        var seg_len = (2 + bim.length/2 + tags.length/2).toString(16);
        if (seg_len.length > 4) {
            console.log("タグのデータ量が限界を超えました");
            return;
        }
        seg_len = "000" + seg_len;
        seg_len = seg_len.slice(-4);
        var exif = "ffed" + seg_len + bim + tags;
        return exif;
    }

    function insertExif(exif, dataurl) {
        var bin = atob(dataurl.split(",")[1]);
        var buffer = new Uint8Array(bin.length + exif.length / 2);
        for (var i = 0; i < 20; i++) {
            buffer[i] = bin.charCodeAt(i);
        }
        for (var bf_i = 20, ex_j = 0; ex_j < exif.length / 2; bf_i++,
             ex_j++) {
            buffer[bf_i] = parseInt(exif.slice(ex_j * 2, ex_j * 2 + 2), 16);
        }
        for (var bf_i = 20 + exif.length / 2, bin_j = 20; bin_j < bin.length; bf_i++,
             bin_j++) {
            buffer[bf_i] = bin.charCodeAt(bin_j);
        }
        var blob = new Blob([buffer.buffer],{
            type: 'image/jpeg'
        });
        return blob;
    }

    //引数はhexで
    function tagLength(str) {
        var len = (str.length / 2).toString(16);
        len = ("0" + len).slice(-2);
        return len;
    }

    //引数はhexで
    function wholeTagLength(str) {
        var len = (str.length / 2).toString(16);
        len = ("000" + len).slice(-4);
        return len;
    }

    function strToHex(str) {
        var hex = "";
        for (var i = 0; i < str.length; i++) {
            hex = hex + str.charCodeAt(i).toString(16);
        }
        return hex;
    }

    function strToU8(str) {
        var buffer = new Uint8Array(str.length);
        for (var i = 0; i < buffer.length; i++) {
            buffer[i] = str.charCodeAt(i);
        }
        return buffer;
    }

    function hexToU8(str) {
        var buffer = new Uint8Array(str.length / 2);
        for (var i = 0; i < buffer.length; i++) {
            buffer[i] = parseInt(str.slice(i * 2, i * 2 + 2), 16);
        }
        return buffer;
    }

    //テスト用 16進数文字列をアルファベットに変換する
    function hexToChar(str) {
        var char = "";
        for (var i = 0; i < str.length / 2; i++) {
            //4a→74→J
            char = char + String.fromCharCode(parseInt(str.slice(i * 2, i * 2 + 2), 16));
        }
        return char;
    }
}
)();