Added argument to freeze for setting the end frame, speed is now aware of variable framerates
This commit is contained in:
		
							parent
							
								
									f21cb21d9c
								
							
						
					
					
						commit
						e00671f0d5
					
				
					 5 changed files with 43 additions and 23 deletions
				
			
		| 
						 | 
					@ -4,6 +4,7 @@
 | 
				
			||||||
      "target_name": "image",
 | 
					      "target_name": "image",
 | 
				
			||||||
      "sources": [ "<!@(node -p \"require('fs').readdirSync('./natives').map(f=>'natives/'+f).join(' ')\")" ],
 | 
					      "sources": [ "<!@(node -p \"require('fs').readdirSync('./natives').map(f=>'natives/'+f).join(' ')\")" ],
 | 
				
			||||||
      "cflags!": [ "-fno-exceptions", "<!(pkg-config --cflags Magick++)" ],
 | 
					      "cflags!": [ "-fno-exceptions", "<!(pkg-config --cflags Magick++)" ],
 | 
				
			||||||
 | 
					      "cflags_cc": [ "-std=c++17" ],
 | 
				
			||||||
      "cflags_cc!": [ "-fno-exceptions", "<!(pkg-config --cflags Magick++)" ],
 | 
					      "cflags_cc!": [ "-fno-exceptions", "<!(pkg-config --cflags Magick++)" ],
 | 
				
			||||||
      "include_dirs": [
 | 
					      "include_dirs": [
 | 
				
			||||||
        "<!@(node -p \"require('node-addon-api').include\")",
 | 
					        "<!@(node -p \"require('node-addon-api').include\")",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,17 @@
 | 
				
			||||||
const ImageCommand = require("../../classes/imageCommand.js");
 | 
					const ImageCommand = require("../../classes/imageCommand.js");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FreezeCommand extends ImageCommand {
 | 
					class FreezeCommand extends ImageCommand {
 | 
				
			||||||
  params = {
 | 
					  params(args) {
 | 
				
			||||||
    loop: false
 | 
					    const frameCount = parseInt(args[0]);
 | 
				
			||||||
  };
 | 
					    return {
 | 
				
			||||||
 | 
					      loop: false,
 | 
				
			||||||
 | 
					      frame: isNaN(frameCount) ? -1 : frameCount
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static description = "Makes an image sequence only play once";
 | 
					  static description = "Makes an image sequence only play once";
 | 
				
			||||||
  static aliases = ["noloop", "once"];
 | 
					  static aliases = ["noloop", "once"];
 | 
				
			||||||
 | 
					  static arguments = ["{end frame number}"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static requiresGIF = true;
 | 
					  static requiresGIF = true;
 | 
				
			||||||
  static noImage = "you need to provide an image to freeze!";
 | 
					  static noImage = "you need to provide an image to freeze!";
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,14 +7,19 @@ using namespace Magick;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FreezeWorker : public Napi::AsyncWorker {
 | 
					class FreezeWorker : public Napi::AsyncWorker {
 | 
				
			||||||
 public:
 | 
					 public:
 | 
				
			||||||
  FreezeWorker(Napi::Function& callback, string in_path, bool loop, string type, int delay)
 | 
					  FreezeWorker(Napi::Function& callback, string in_path, bool loop, int frame, string type, int delay)
 | 
				
			||||||
      : Napi::AsyncWorker(callback), in_path(in_path), loop(loop), type(type), delay(delay) {}
 | 
					      : Napi::AsyncWorker(callback), in_path(in_path), loop(loop), frame(frame), type(type), delay(delay) {}
 | 
				
			||||||
  ~FreezeWorker() {}
 | 
					  ~FreezeWorker() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void Execute() {
 | 
					  void Execute() {
 | 
				
			||||||
    list <Image> frames;
 | 
					    list <Image> frames;
 | 
				
			||||||
    readImages(&frames, in_path);
 | 
					    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(), animationIterationsImage(loop ? 0 : 1));
 | 
				
			||||||
    for_each(frames.begin(), frames.end(), magickImage(type));
 | 
					    for_each(frames.begin(), frames.end(), magickImage(type));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -28,7 +33,7 @@ class FreezeWorker : public Napi::AsyncWorker {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 private:
 | 
					 private:
 | 
				
			||||||
  string in_path, type;
 | 
					  string in_path, type;
 | 
				
			||||||
  int delay;
 | 
					  int frame, delay;
 | 
				
			||||||
  Blob blob;
 | 
					  Blob blob;
 | 
				
			||||||
  bool loop;
 | 
					  bool loop;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -43,8 +48,9 @@ Napi::Value Freeze(const Napi::CallbackInfo &info)
 | 
				
			||||||
  bool loop = obj.Has("loop") ? obj.Get("loop").As<Napi::Boolean>().Value() : false;
 | 
					  bool loop = obj.Has("loop") ? obj.Get("loop").As<Napi::Boolean>().Value() : false;
 | 
				
			||||||
  string type = obj.Get("type").As<Napi::String>().Utf8Value();
 | 
					  string type = obj.Get("type").As<Napi::String>().Utf8Value();
 | 
				
			||||||
  int delay = obj.Has("delay") ? obj.Get("delay").As<Napi::Number>().Int32Value() : 0;
 | 
					  int delay = obj.Has("delay") ? obj.Get("delay").As<Napi::Number>().Int32Value() : 0;
 | 
				
			||||||
 | 
					  int frame = obj.Has("frame") ? obj.Get("frame").As<Napi::Number>().Int32Value() : -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  FreezeWorker* blurWorker = new FreezeWorker(cb, path, loop, type, delay);
 | 
					  FreezeWorker* freezeWorker = new FreezeWorker(cb, path, loop, frame, type, delay);
 | 
				
			||||||
  blurWorker->Queue();
 | 
					  freezeWorker->Queue();
 | 
				
			||||||
  return env.Undefined();
 | 
					  return env.Undefined();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -15,26 +15,28 @@ class SpeedWorker : public Napi::AsyncWorker {
 | 
				
			||||||
    list <Image> frames;
 | 
					    list <Image> frames;
 | 
				
			||||||
    readImages(&frames, in_path);
 | 
					    readImages(&frames, in_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    int old_delay = 0;
 | 
					 | 
				
			||||||
    // if passed a delay, use that. otherwise use the average frame delay.
 | 
					    // 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) {
 | 
					    if (delay == 0) {
 | 
				
			||||||
      for (Image &image : frames) {
 | 
					      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 {
 | 
					    } else {
 | 
				
			||||||
      old_delay = delay;
 | 
					      int new_delay = slow ? delay * 2 : delay / 2;
 | 
				
			||||||
    }
 | 
					      if (!slow && new_delay <= 1) {
 | 
				
			||||||
 | 
					        new_delay = delay;
 | 
				
			||||||
    int new_delay = slow ? old_delay * 2 : old_delay / 2;
 | 
					        auto it = frames.begin();
 | 
				
			||||||
    if (!slow && new_delay <= 1) {
 | 
					        while(it != frames.end() && ++it != frames.end()) it = frames.erase(it);
 | 
				
			||||||
      new_delay = old_delay;
 | 
					      } else {
 | 
				
			||||||
      auto it = frames.begin();
 | 
					        for_each(frames.begin(), frames.end(), animationDelayImage(new_delay));
 | 
				
			||||||
      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));
 | 
					    for_each(frames.begin(), frames.end(), magickImage(type));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,6 +35,9 @@ const getImage = async (image, image2, video, gifv = false) => {
 | 
				
			||||||
    if (gifv) {
 | 
					    if (gifv) {
 | 
				
			||||||
      const host = new URL(image2).host;
 | 
					      const host = new URL(image2).host;
 | 
				
			||||||
      if (tenorURLs.includes(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 !== "") {
 | 
					        if (process.env.TENOR !== "") {
 | 
				
			||||||
          const data = await fetch(`https://api.tenor.com/v1/gifs?ids=${image2.split("-").pop()}&key=${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();
 | 
					          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];
 | 
					          payload.delay = (100 / delay.split("/")[0]) * delay.split("/")[1];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      } else if (giphyURLs.includes(host)) {
 | 
					      } 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`;
 | 
					        payload.path = `https://media0.giphy.com/media/${image2.split("-").pop()}/giphy.gif`;
 | 
				
			||||||
      } else if (imgurURLs.includes(host)) {
 | 
					      } else if (imgurURLs.includes(host)) {
 | 
				
			||||||
 | 
					        // Seems that Tenor has a possibility of making GIFs static
 | 
				
			||||||
        payload.path = image.replace(".mp4", ".gif");
 | 
					        payload.path = image.replace(".mp4", ".gif");
 | 
				
			||||||
      } else if (gfycatURLs.includes(host)) {
 | 
					      } 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.path = `https://thumbs.gfycat.com/${image.split("/").pop().split(".mp4")[0]}-size_restricted.gif`;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      payload.type = "image/gif";
 | 
					      payload.type = "image/gif";
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue