diff options
-rw-r--r-- | Makefile | 69 | ||||
-rw-r--r-- | README | 4 | ||||
-rwxr-xr-x | aencode.sh | 25 | ||||
-rwxr-xr-x | iencode.sh | 32 | ||||
-rwxr-xr-x | setup.sh | 9 | ||||
-rw-r--r-- | unrpyc/Makefile | 4 | ||||
-rwxr-xr-x | vencode.sh | 23 | ||||
-rw-r--r-- | www/js/api.js | 235 |
8 files changed, 183 insertions, 218 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5b0b99d --- /dev/null +++ b/Makefile @@ -0,0 +1,69 @@ +FFMPEG ?= ffmpeg +FFMPEG += -v warning $(FFMPEGFLAGS) +OPUSENC ?= opusenc +ZOPFLIPNG ?= zopflipng +JPEGOPTIM ?= jpegoptim +CWEBP ?= cwebp + +DUMP ?= www/dump + +all: video audio images + +# === VIDEO === + +VIDEO := $(wildcard $(DUMP)/video/*.mkv) +MP4 := $(patsubst %.mkv,%.mp4,$(VIDEO)) +WEBM := $(patsubst %.mkv,%.webm,$(VIDEO)) +OGV := $(patsubst %.mkv,%.ogv,$(VIDEO)) +CVIDEO := $(MP4) $(WEBM) $(OGV) + +video: $(CVIDEO) + +%.mp4: %.mkv + $(FFMPEG) -i $< -c:v libx264 -preset slower -tune animation -c:a libfdk_aac $@ + +%.webm: %.mkv + $(FFMPEG) -i $< -c:v libvpx -crf 15 -b:v 1M -c:a copy $@ + +%.ogv: %.mkv + $(FFMPEG) -i $< -c:v libtheora -qscale:v 6 -c:a copy $@ + +# === AUDIO === + +AUDIO := $(shell find $(DUMP)/bgm $(DUMP)/sfx -name '*.ogg') +OPUS := $(patsubst %.ogg,%.opus,$(AUDIO)) +M4A := $(patsubst %.ogg,%.m4a,$(AUDIO)) +CAUDIO := $(OPUS) $(M4A) + +audio: $(CAUDIO) + +%.wav: %.ogg + $(FFMPEG) -i $< -c:a pcm_s16le $@ + +%.opus: %.wav + $(OPUSENC) --bitrate 64 $< $@ + +%.m4a: %.wav + $(FFMPEG) -i $< -c:a libfdk_aac -vbr 2 $@ + +# === IMAGES === + +PNG := $(shell find $(DUMP) -name '*.png') +JPG := $(shell find $(DUMP) -name '*.jpg') +WEBP := $(patsubst %.png,%.webp,$(PNG)) \ + $(patsubst %.jpg,%.webp,$(JPG)) + +images: $(WEBP) + +%.webp: %.png + $(ZOPFLIPNG) -m -y $< $< + $(CWEBP) -q 99 -m 6 $< -o $@ + +%.webp: %.jpg + $(JPEGOPTIM) --strip-all $< + $(CWEBP) -q 90 -m 6 $< -o $@ + +clean: + rm $(CVIDEO) $(CAUDIO) $(WEBP) + +.PHONY: video audio images clean @@ -5,17 +5,17 @@ jQuery is not used, but when.js [1] is used as a sensible Promises/A+ implementa == Requirements == - Katawa Shoujo (obviously) - when.js -- Bash - Firefox/Chrome - ffmpeg with AAC, libtheora, libvpx, libx264 - opus-tools - libwebp +- make (non-GNU untested) How to use: 0. Get Katawa Shoujo. 1. Copy *.rpyc from Katawa Shoujo/game into unrpyc/ directory. 2. Extract files from data.rpa with an appropriate tool. Put files in www/dump. -3. Run setup.sh [2]. Use environment variable THREADS to control number of threads/processes spawned. +3. Run make. 4. Run nginx.sh [2] to start nginx with appropriate options for development, then connect to localhost:8080. -- or -- diff --git a/aencode.sh b/aencode.sh deleted file mode 100755 index af101be..0000000 --- a/aencode.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -# configure ffmpeg location if not in PATH -FFMPEG=ffmpeg -# configure flags -FFMPEG_FLAGS="" - -set -e - -ffmpeg() { - set -x - command ${FFMPEG} -threads ${THREADS} ${FFMPEG_FLAGS} "$@" - set +x -} - -for d in bgm sfx; do - pushd $(dirname $0)/www/dump/${d} - for f in *.ogg; do - OUT=${f%.ogg} - ffmpeg -y -i $f -c:a pcm_s16le ${OUT}.wav - opusenc --bitrate 64 ${OUT}.wav ${OUT}.opus - ffmpeg -n -i ${OUT}.wav -c:a libfdk_aac -vbr 2 ${OUT}.m4a || true - done - popd -done diff --git a/iencode.sh b/iencode.sh deleted file mode 100755 index ac898d7..0000000 --- a/iencode.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -# configure cwebp location if not in PATH -CWEBP=cwebp -# configure flags -CWEBP_FLAGS="-m 6" - -set -e - -cd $(dirname $0)/www/dump - -iencode() { - EXT="$1" - QUAL="$2" - export EXT QUAL CWEBP CWEBP_FLAGS - set -x - find . -name \*."${EXT}" -print0 | xargs -0 -P ${THREADS} -n 1 bash -c ' - IN="$0" - OUT="${IN%.${EXT}}.webp" - [[ -f ${OUT} ]] || ${CWEBP} -q "${QUAL}" ${CWEBP_FLAGS} ${IN} -o ${OUT} - ' -} - -iencode jpg 90 -iencode png 99 - -if hash zopflipng; then - find . -name \*.png -print0 | xargs -0 -I '{}' -P ${THREADS} zopflipng -m -y '{}' '{}' -else - echo >&2 "Install zopfli (https://code.google.com/p/zopfli/) to improve PNG compression." - echo >&2 "KS 1.1 .png files reduce by ~2.6M (~1.6%) with -m, taking several hours." -fi @@ -1,7 +1,5 @@ #!/bin/bash -export THREADS=${THREADS:-$(nproc)} - cd $(dirname $0) git submodule update --init @@ -10,8 +8,6 @@ pushd unrpyc make install popd -./vencode.sh - trim() { convert -trim "$@" "$@" optipng -o7 "$@" @@ -20,7 +16,6 @@ trim() { trim www/dump/ui/bt-cf-unchecked.png trim www/dump/ui/bt-cf-checked.png -convert www/dump/ui/icon.png -resize 256x256 -transparent white www/favicon.ico +make -./iencode.sh -./aencode.sh +convert www/dump/ui/icon.png -resize 256x256 -transparent white www/favicon.ico diff --git a/unrpyc/Makefile b/unrpyc/Makefile index 431e10b..a92a91d 100644 --- a/unrpyc/Makefile +++ b/unrpyc/Makefile @@ -1,9 +1,9 @@ -gzip := $(shell ./find-gzip.sh) +GZIP := $(shell ./find-gzip.sh) all: script.json script.json.gz imachine.json imachine.json.gz imachine_replay.json imachine_replay.json.gz ui-strings.json ui-strings.json.gz %.json.gz: %.json - $(gzip) -c $< > $@ + $(GZIP) -c $< > $@ touch $< $@ script.json: $(patsubst %.rpyc,%.json,$(wildcard script-*.rpyc)) diff --git a/vencode.sh b/vencode.sh deleted file mode 100755 index 2b8233e..0000000 --- a/vencode.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -# configure ffmpeg location if not in PATH -FFMPEG=ffmpeg -# configure flags -FFMPEG_FLAGS="" - -set -e - -cd $(dirname $0)/www/dump/video - -ffmpeg() { - set -x - command ${FFMPEG} -threads ${THREADS} ${FFMPEG_FLAGS} "$@" - set +x -} - -for f in *.mkv; do - OUT=${f%.mkv} - ffmpeg -i $f -c:v libx264 -preset slower -tune animation -c:a libfdk_aac ${OUT}.mp4 - ffmpeg -i $f -c:v libvpx -crf 15 -b:v 1M -c:a copy ${OUT}.webm - ffmpeg -i $f -c:v libtheora -qscale:v 6 -c:a copy ${OUT}.ogv -done diff --git a/www/js/api.js b/www/js/api.js index 4aa6f5f..869b1cf 100644 --- a/www/js/api.js +++ b/www/js/api.js @@ -1,85 +1,34 @@ "use strict"; window.html5ks.api = { init: function () { - - // tag character names var chars = html5ks.data.characters; for (var ch in chars) { - var chr = chars[ch]; - if (chr.name) { - chr.name = this.tag(chr.name); + var char = chars[ch]; + if (char.name) { + char.name = this.tag(char.name); } } - - }, - - - iscene: function (target, is_h, is_end) { - - // TODO: implement timeskip - if (target === "timeskip") { - deferred.resolve(); - return deferred.promise; - } - - html5ks.state.status = "scene"; - - var deferred = when.defer(), - label = html5ks.data.script[html5ks.persistent.language + "_" + target], - i = 0; - - var run = function (ret) { - if (label[i]) { - this.runInst(label[i++]).then(run, console.error); - } else { - deferred.resolve(ret); - } - }.bind(this); - - run(); - - return deferred.promise; }, - - runInst: function (inst) { - var cmd = inst[0].replace(/"/g, ''), - args = inst.slice(1); - if (html5ks.data.characters[cmd]) { - return this.character(cmd, args[0]); - } else { - if (this[cmd]) { - return this[cmd].apply(this, args); - } else if (inst.length === 1) { - return this.character("name_only", cmd); - } else if (/^[A-Z]/.test(cmd)) { - return this.character(cmd, args[0]); - } else { - console.error("no such cmd " + cmd); - return when.defer().resolve(); - } - } - }, - - _fades: {}, + _fading: {}, set_volume: function (target, delay, channel) { var deferred = when.defer(), - audioElement = html5ks.elements.audio[channel], - step = (target - audioElement.volume) / (delay * 20); + audio = html5ks.elements.audio[channel], + step = (target - audio.volume) / (delay * 20); if (!delay) { - audioElement.volume = target; + audio.volume = target; } else { - if (this._fades[channel]) clearInterval(this._fades[channel]); - this._fades[channel] = setInterval(function () { + this._fading[channel] = setInterval(function () { // clamp new volume 0-1 - audioElement.volume = Math.min(Math.max(audioElement.volume + step, 0), 1); - if (audioElement.volume === 0 || audioElement.volume === 1) { - clearInterval(this._fades[channel]); + audio.volume = Math.min(Math.max(audio.volume + step, 0), 1); + if (audio.volume === 0 || audio.volume === 1) { + clearInterval(this._fading); } }.bind(this), 50); } - return deferred.resolve(); + deferred.resolve(); + return deferred.promise; }, play: function (channel, name, ignore, fade) { @@ -102,12 +51,17 @@ window.html5ks.api = { src += "sfx/" + html5ks.data.sfx[name]; } - ["opus", "ogg", "m4a", "wav"].some(function (type) { - if (Modernizr.audio[type]) { - audio.src = src + "." + type; - return true; - } - }); + if (Modernizr.audio.opus) { + audio.src = src + ".opus"; + } else if (Modernizr.audio.ogg) { + audio.src = src + ".ogg"; + } else if (Modernizr.audio.m4a) { + audio.src = src + ".m4a"; + } else if (Modernizr.audio.wav) { + audio.src = src + ".wav"; + } else { + console.error("wtf, no audio formats"); + } audio.load(); var volume = html5ks.persistent[channel + "Volume"]; @@ -128,19 +82,15 @@ window.html5ks.api = { stop: function (channel, ignore, fade) { if (channel === "all") { - return ["music", "sound", "ambient"].forEach(function (channel) { - this.stop(channel, ignore, fade); - }.bind(this)); + this.stop("music", ignore, fade); + this.stop("sound", ignore, fade); + return this.stop("ambient", ignore, fade); } - var deferred = when.defer(), audio = html5ks.elements.audio[channel]; - - // clear fade - if (this._fades[channel]) { - clearInterval(this._fades[channel]); + if (this._fading[channel]) { + clearInterval(this._fading[channel]); } - if (fade) { this.set_volume(0, fade, channel); } else { @@ -153,31 +103,27 @@ window.html5ks.api = { movie_cutscene: function (vid_src, skippable) { var deferred = when.defer(), - video = html5ks.elements.video; + video = html5ks.elements.video, + src = "dump/video/" + vid_src + "."; this.stop("all"); this.speed("auto", false); this.speed("skip", false); - var types = { - webm: "webm", - ogg: "ogv", - h264: "mp4" - }; - for (var type in types) { - if (Modernizr.video[type]) { - video.src = "dump/video/" + vid_src + "." + types[type]; - break; - } + if (Modernizr.video.webm) { + video.src = src + "webm"; + } else if (Modernizr.video.ogg) { + video.src = src + "ogv"; + } else if (Modernizr.video.h264) { + video.src = src + "mp4"; + } else { + console.error("wtf is this, no video formats"); } - video.oncanplaythrough = function () { - video.volume = html5ks.persistent.musicVolume; - video.style.display = "block"; - video.play(); - video.onended = done; - }; - + video.load(); + video.style.display = "block"; + video.volume = html5ks.persistent.musicVolume; + video.play(); var done = function () { video.style.display = "none"; video.pause(); @@ -195,13 +141,10 @@ window.html5ks.api = { done(); } }; - + video.onended = done; video.onerror = function () { deferred.reject(this.error); }; - - video.load(); - return deferred.promise; }, @@ -211,19 +154,60 @@ window.html5ks.api = { }, + iscene: function (target, is_h, is_end) { + html5ks.state.status = "scene"; + var deferred = when.defer(), + label = html5ks.data.script[html5ks.persistent.language + "_" + target], + i = 0; + (function run(ret) { + if (label[i]) { + html5ks.api.runInst(label[i]).then(run, console.error); + i++; + } else { + deferred.resolve(ret); + } + }()); + return deferred.promise; + }, + + + runInst: function (inst) { + var cmd = inst[0].replace(/"/g, ''), + args = inst.slice(1); + if (html5ks.data.characters[cmd]) { + return this.character(cmd, args[0]); + } else { + if (this[cmd]) { + return this[cmd].apply(this, args); + } else if (inst.length === 1) { + return this.character("name_only", cmd); + } else if (/^[A-Z]/.test(cmd)) { + return this.character(cmd, args[0]); + } else { + console.error("no such cmd " + cmd); + var deferred = when.defer(); + deferred.resolve(); + return deferred.promise; + } + } + }, + + window: function (action, transition) { - var windowEl = html5ks.elements.window; + var windw = html5ks.elements.window, + deferred = when.defer(); switch (action) { case "show": - windowEl.style.display = "block"; + windw.style.display = "block"; break; case "hide": - windowEl.style.display = "none"; + windw.style.display = "none"; break; default: - return windowEl.style.display !== "none"; + return windw.style.display !== "none"; } - return when.defer().resolve(action); + deferred.resolve(action); + return deferred.promise; }, @@ -322,20 +306,18 @@ window.html5ks.api = { } return deferred.promise; }, - hide: function (name) { var deferred = when.defer(); var show = html5ks.elements.show.children; for (var i = show.length - 1; i >= 0; i--) { if (show[i].id === name) { - show[i].style.display = "none"; - deferred.resolve(); - return deferred.promise; + html5ks.elements.show.removeChild(show[i]); } } + deferred.resolve(); + return deferred.promise; }, - tag: function (str) { var tags = [ /&/g, "&", @@ -364,29 +346,29 @@ window.html5ks.api = { character: function (charName, str, extend) { var deferred = when.defer(), text = this.tag(str), - chr = typeof charName === "string" ? html5ks.data.characters[charName] : charName, + char = typeof charName === "string" ? html5ks.data.characters[charName] : charName, w = /{w=?(\d*\.\d*)?}(.*)/.exec(str); - if (!chr) { - chr = { + if (!char) { + char = { name: charName }; } - if (typeof chr.what_prefix === "undefined") { - chr.what_prefix = "“"; - chr.what_suffix = "”"; + if (typeof char.what_prefix === "undefined") { + char.what_prefix = "“"; + char.what_suffix = "”"; } - this._lastchar = chr; + this._lastchar = char; - if (!extend && chr.what_prefix) { - text = chr.what_prefix + text; + if (!extend && char.what_prefix) { + text = char.what_prefix + text; } - if ((!w || !w[1]) && chr.what_suffix) { - text = text + chr.what_suffix; + if ((!w || !w[1]) && char.what_suffix) { + text = text + char.what_suffix; } - if (chr.kind === "nvl") { + if (char.kind === "nvl") { html5ks.elements.nvlsay.innerHTML += "<span class='nvl-block'>" + text + "</span>"; html5ks.elements.nvlctc.style.display = "block"; html5ks.next = function () { @@ -397,9 +379,9 @@ window.html5ks.api = { } else { var who = html5ks.elements.who; if (!extend) { - who.innerHTML = chr.name; - if (chr.color) { - who.style.color = chr.color; + who.innerHTML = char.name; + if (char.color) { + who.style.color = char.color; } else { who.style.color = "#ffffff"; } @@ -487,7 +469,6 @@ window.html5ks.api = { }, menu: function (choices) { - var args = Array.prototype.slice.call(arguments, 0); var deferred = when.defer(); var menu = html5ks.elements.choices, frag = document.createDocumentFragment(), |