diff --git a/binding.gyp b/binding.gyp index 28b0225..93d3eff 100644 --- a/binding.gyp +++ b/binding.gyp @@ -4,6 +4,7 @@ "target_name": "image", "sources": [ "'natives/'+f).join(' ')\")" ], "cflags!": [ "-fno-exceptions", " frames; readImages(&frames, in_path); + if (frame >= 0 && !loop) { + size_t frameSize = frames.size(); + int framePos = clamp(frame, 0, (int)frameSize); + frames.resize(framePos + 1); + } for_each(frames.begin(), frames.end(), animationIterationsImage(loop ? 0 : 1)); for_each(frames.begin(), frames.end(), magickImage(type)); @@ -28,7 +33,7 @@ class FreezeWorker : public Napi::AsyncWorker { private: string in_path, type; - int delay; + int frame, delay; Blob blob; bool loop; }; @@ -43,8 +48,9 @@ Napi::Value Freeze(const Napi::CallbackInfo &info) bool loop = obj.Has("loop") ? obj.Get("loop").As().Value() : false; string type = obj.Get("type").As().Utf8Value(); int delay = obj.Has("delay") ? obj.Get("delay").As().Int32Value() : 0; + int frame = obj.Has("frame") ? obj.Get("frame").As().Int32Value() : -1; - FreezeWorker* blurWorker = new FreezeWorker(cb, path, loop, type, delay); - blurWorker->Queue(); + FreezeWorker* freezeWorker = new FreezeWorker(cb, path, loop, frame, type, delay); + freezeWorker->Queue(); return env.Undefined(); } \ No newline at end of file diff --git a/natives/speed.cc b/natives/speed.cc index 92dc138..5df69ce 100644 --- a/natives/speed.cc +++ b/natives/speed.cc @@ -15,26 +15,28 @@ class SpeedWorker : public Napi::AsyncWorker { list frames; readImages(&frames, in_path); - int old_delay = 0; // if passed a delay, use that. otherwise use the average frame delay. - // GIFs can have a variable framerate, and the frameskipping logic here doesn't handle that. - // TODO: revisit? if (delay == 0) { for (Image &image : frames) { - old_delay += image.animationDelay(); + int old_delay = image.animationDelay(); + int new_delay = slow ? old_delay * 2 : old_delay / 2; + if (!slow && new_delay <= 1) { + new_delay = delay; + auto it = frames.begin(); + while(it != frames.end() && ++it != frames.end()) it = frames.erase(it); + } else { + image.animationDelay(new_delay); + } } - old_delay /= frames.size(); } else { - old_delay = delay; - } - - int new_delay = slow ? old_delay * 2 : old_delay / 2; - if (!slow && new_delay <= 1) { - new_delay = old_delay; - auto it = frames.begin(); - while(it != frames.end() && ++it != frames.end()) it = frames.erase(it); - } else { - for_each(frames.begin(), frames.end(), animationDelayImage(new_delay)); + int new_delay = slow ? delay * 2 : delay / 2; + if (!slow && new_delay <= 1) { + new_delay = delay; + auto it = frames.begin(); + while(it != frames.end() && ++it != frames.end()) it = frames.erase(it); + } else { + for_each(frames.begin(), frames.end(), animationDelayImage(new_delay)); + } } for_each(frames.begin(), frames.end(), magickImage(type)); diff --git a/utils/imagedetect.js b/utils/imagedetect.js index 401240b..1d39633 100644 --- a/utils/imagedetect.js +++ b/utils/imagedetect.js @@ -35,6 +35,9 @@ const getImage = async (image, image2, video, gifv = false) => { if (gifv) { const host = new URL(image2).host; if (tenorURLs.includes(host)) { + // Tenor doesn't let us access a raw GIF without going through their API, + // so we use that if there's a key in the config and fall back to using the MP4 if there isn't + // Note that MP4 conversion requires an ImageMagick build that supports MPEG decoding if (process.env.TENOR !== "") { const data = await fetch(`https://api.tenor.com/v1/gifs?ids=${image2.split("-").pop()}&key=${process.env.TENOR}`); const json = await data.json(); @@ -44,10 +47,13 @@ const getImage = async (image, image2, video, gifv = false) => { payload.delay = (100 / delay.split("/")[0]) * delay.split("/")[1]; } } else if (giphyURLs.includes(host)) { + // Can result in an HTML page instead of a GIF payload.path = `https://media0.giphy.com/media/${image2.split("-").pop()}/giphy.gif`; } else if (imgurURLs.includes(host)) { + // Seems that Tenor has a possibility of making GIFs static payload.path = image.replace(".mp4", ".gif"); } else if (gfycatURLs.includes(host)) { + // iirc Gfycat also seems to sometimes make GIFs static payload.path = `https://thumbs.gfycat.com/${image.split("/").pop().split(".mp4")[0]}-size_restricted.gif`; } payload.type = "image/gif";