Skip to content

Instantly share code, notes, and snippets.

@whiteball
Last active December 28, 2022 15:08
Show Gist options
  • Save whiteball/03c4953d7f547187d979267f5ef36c59 to your computer and use it in GitHub Desktop.
Save whiteball/03c4953d7f547187d979267f5ef36c59 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name TrinArtで生成画像とパラメータをまとめて自動でダウンロード
// @namespace https://ai-novelist-share.geo.jp/
// @version 0.4.1
// @description TrinArtの生成ページで、画像を生成するごとに生成画像とパラメータをまとめて自動でダウンロードするオプションを追加します。
// @author しらたま
// @match https://ai-novel.com/art/
// @match https://ai-novel.com/art/index.php*
// @icon https://www.google.com/s2/favicons?sz=64&domain=ai-novel.com
// @updateURL https://gist.github.com/whiteball/03c4953d7f547187d979267f5ef36c59/raw/ai_novelist_trinart_download_button.user.js
// @downloadURL https://gist.github.com/whiteball/03c4953d7f547187d979267f5ef36c59/raw/ai_novelist_trinart_download_button.user.js
// @supportURL https://gist.github.com/whiteball/03c4953d7f547187d979267f5ef36c59
// @grant none
// @require https://cdnjs.cloudflare.com/ajax/libs/crc-32/1.2.2/crc32.min.js
// ==/UserScript==
(function() {
'use strict';
const isSmartPhone = /iPhone|Android.+Mobile/.test(navigator.userAgent)
let pref = localStorage.getItem('mod_trinart_pref') ? JSON.parse(localStorage.getItem('mod_trinart_pref')) : {}
let radio_list = ['mod_auto_save', 'mod_auto_save_emb', 'mod_auto_save_none']
if (isSmartPhone) {
document.getElementById('current_req_lumina').previousElementSibling.insertAdjacentHTML('beforebegin', `<h3>自動保存(ユーザースクリプト)</h3>
<div><label><input type="radio" style="font-size: 18px; transform:scale(1.5);" value="2" id="mod_auto_save_emb" name="mod_auto_save" ${(pref.auto_save === true || pref.auto_save === '1' || pref.auto_save === '2') ? 'checked' : ''}> 生成画像の自動保存(テキスト埋め込み)</label>
<div class="explanations"style="margin-bottom: 10px">画像を生成した直後にパラメータを画像に埋め込み、PNGとしてダウンロードを開始します。</div>
<label><input type="radio" style="font-size: 18px; transform:scale(1.5);" value="0" id="mod_auto_save_none" name="mod_auto_save" ${(!(pref.auto_save === true || pref.auto_save === '1' || pref.auto_save === '2')) ? 'checked' : ''}> 自動保存しない</label>
<div class="explanations">自動ダウンロードをしません。</div>
</div><br><br>`)
radio_list = ['mod_auto_save_emb', 'mod_auto_save_none']
} else {
document.getElementById('current_req_lumina').previousElementSibling.insertAdjacentHTML('beforebegin', `<h3>自動保存(ユーザースクリプト)</h3>
<div><label><input type="radio" style="font-size: 18px; transform:scale(1.5);" value="1" id="mod_auto_save" name="mod_auto_save" ${(pref.auto_save === true || pref.auto_save === '1') ? 'checked' : ''}> 生成画像の自動保存</label>
<div class="explanations" style="margin-bottom: 10px">画像を生成した直後に画像とパラメータのダウンロードを開始します。</div>
<label><input type="radio" style="font-size: 18px; transform:scale(1.5);" value="2" id="mod_auto_save_emb" name="mod_auto_save" ${(pref.auto_save === '2') ? 'checked' : ''}> 生成画像の自動保存(テキスト埋め込み)</label>
<div class="explanations"style="margin-bottom: 10px">画像を生成した直後にパラメータを画像に埋め込み、PNGとしてダウンロードを開始します。</div>
<label><input type="radio" style="font-size: 18px; transform:scale(1.5);" value="0" id="mod_auto_save_none" name="mod_auto_save" ${(!(pref.auto_save === true || pref.auto_save === '1' || pref.auto_save === '2')) ? 'checked' : ''}> 自動保存しない</label>
<div class="explanations">自動ダウンロードをしません。</div>
</div><br><br>`)
}
for (const name of radio_list) {
const mod_auto_save = document.getElementById(name)
if (mod_auto_save) {
mod_auto_save.addEventListener('change', function () {
pref.auto_save = this.value
localStorage.setItem('mod_trinart_pref', JSON.stringify(pref))
})
}
}
const originalShowpost = window.showpost
window.showpost = function () {
const mod_download_name = document.getElementById('mod_download_name')
if (mod_download_name) {
mod_download_name.style.display = ''
}
originalShowpost()
}
document.body.insertAdjacentHTML('afterbegin','<canvas id="mod_canvas" style="display: none;"></canvas>')
const mod_canvas = document.getElementById('mod_canvas')
const context = mod_canvas.getContext('2d')
const downloadImageWithEmbededParam = function (div){
const temp_data = JSON.parse(div.innerText)
const local_image = div.getAttribute('mod-file')
const softener_strength_array = ['無し', "弱", "弱", "中", "中", "強", "強"]
const complexity_array = ['構図モード', "精度モード"]
const text = `プロンプト: ${temp_data.text}
画像のサイズ: ${temp_data.width}x${temp_data.height}
ステップ数: ${temp_data.steps} steps
プロンプトの強さ/色の濃さ: ${temp_data.guidance}
重ね描きの強さ: ${Math.floor(temp_data.strength*100)}%
スタイルソフテナ: ${softener_strength_array[temp_data.softener]}
固定シード: ${temp_data.seed}
サンプラー: ${temp_data.sampler_type}
参考にする画像のURL: ${temp_data.init_image_url}
参考にする画像ファイル: ${local_image}
モデル/プロンプトタイプ: ${temp_data.prompt_type}
作画モード: ${complexity_array[temp_data.complexity]}
拡張プリセット: ${temp_data.neg_enhancer}`;
const title = temp_data.seed + '_' + document.getElementById('mod_download_name').value
const img = new Image()
img.addEventListener('load', function () {
mod_canvas.width = img.naturalWidth
mod_canvas.height = img.naturalHeight
context.drawImage(img, 0, 0)
mod_canvas.toBlob(blob => {
const reader = new FileReader()
reader.addEventListener('load', function () {
// チャンクを挿入する位置で分割
const head = new Uint8Array(reader.result.slice(0, 33))
const tail = new Uint8Array(reader.result.slice(33))
// プロンプトをバイト配列に変k何
const text_encoder = new TextEncoder()
const chank_data = text_encoder.encode(text)
// テキストチャンクのヘッダ
// let size = 11 + chank_data.length
// const chunk_head = Uint8Array.from([(size&0xff000000)>>24, (size&0xff0000)>>16, (size&0xff00)>>8, size&0xff, 0x74, 0x45, 0x58, 0x74, 0x70, 0x61, 0x72, 0x61, 0x6D, 0x65, 0x74, 0x65, 0x72, 0x73, 0x00])
let size = 20 + chank_data.length
const chunk_head = Uint8Array.from([(size&0xff000000)>>24, (size&0xff0000)>>16, (size&0xff00)>>8, size&0xff, 0x69, 0x54, 0x58, 0x74, 0x70, 0x61, 0x72, 0x61, 0x6D, 0x65, 0x74, 0x65, 0x72, 0x73, 0x00, 0x00, 0x00, 106, 97, 45, 74, 80, 0, 0])
// 書き込み領域確保
const png_data = new Uint8Array(head.length + chunk_head.length + chank_data.length + 4/*crc*/ + tail.length)
// 書き込み
png_data.set(head)
png_data.set(chunk_head, head.length)
png_data.set(chank_data, head.length + chunk_head.length)
// TypeとDataのCRC32
const crc_target = png_data.slice(head.length + 4, head.length + chunk_head.length + chank_data.length)
const crc = CRC32.buf(crc_target)
png_data.set(Uint8Array.from([(crc&0xff000000)>>24, (crc&0xff0000)>>16, (crc&0xff00)>>8, crc&0xff]), head.length + chunk_head.length + chank_data.length)
png_data.set(tail, head.length + chunk_head.length + chank_data.length + 4)
// PNGとしてダウンロード
const img_blob = new Blob([png_data], { type: 'image/png' })
const a = document.createElement("a")
document.body.appendChild(a)
a.href = URL.createObjectURL(img_blob)
a.download = title + '.png'
a.click()
URL.revokeObjectURL(a.href)
document.body.removeChild(a)
})
reader.readAsArrayBuffer(blob)
}, 'image/png')
})
img.src = 'data:image/jpeg;base64,' + div.getAttribute('img_val')
}
const originalUndoRedoLabel = window.UndoRedoLabel
window.UndoRedoLabel = function () {
originalUndoRedoLabel()
const div = document.querySelector('.undo_img:nth-child(' + (Number(document.getElementById('cur_undo_pos').value) + 1) + ')')
document.getElementById('mod_download_name').value = div.getAttribute('mod-title')
const mod_auto_save = document.getElementById('mod_auto_save_emb')
const submit_area = document.getElementById('submit_area')
if (mod_auto_save && mod_auto_save.checked && submit_area && submit_area.style.display === 'none') {
if (!div) { return; }
downloadImageWithEmbededParam(div)
}
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment