Skip to content

Commit

Permalink
Update gif.ski to 1.11.0 (#306)
Browse files Browse the repository at this point in the history
  • Loading branch information
kornelski committed May 6, 2023
1 parent 629168a commit 052e4ab
Show file tree
Hide file tree
Showing 11 changed files with 443 additions and 412 deletions.
309 changes: 169 additions & 140 deletions gifski-api/Cargo.lock

Large diffs are not rendered by default.

23 changes: 12 additions & 11 deletions gifski-api/Cargo.toml
Expand Up @@ -10,10 +10,10 @@ license = "AGPL-3.0-or-later"
name = "gifski"
readme = "README.md"
repository = "https://github.com/ImageOptim/gifski"
version = "1.10.2"
version = "1.11.0"
autobins = false
edition = "2021"
rust-version = "1.63"
rust-version = "1.65"

[[bin]]
doctest = false
Expand All @@ -26,20 +26,20 @@ required-features = ["png"]

[dependencies]
gifsicle = { version = "1.93.0", optional = true }
clap = { version = "4.1.1", features = ["cargo"], optional = true }
clap = { version = "4.2.5", features = ["cargo"], optional = true }
gif = { version = "0.12.0", default-features = false, features = ["std", "raii_no_panic"] }
gif-dispose = "4.0.0"
imagequant = "4.1.0"
imagequant = "4.2.0"
imgref = "1.9.4"
lodepng = { version = "3.7.2", optional = true }
pbr = { version = "1.0.4", optional = true }
pbr = { version = "1.1.1", optional = true }
resize = "0.7.4"
rgb = "0.8.34"
rgb = "0.8.36"
wild = { version = "2.1.0", optional = true }
natord = { version = "1.0.9", optional = true }
quick-error = "2.0.1"
dunce = { version = "1.0.3", optional = true }
crossbeam-channel = "0.5.6"
dunce = { version = "1.0.4", optional = true }
crossbeam-channel = "0.5.8"
loop9 = "0.1.3"
# noisy-float 0.2 bug
num-traits = { version = "0.2.15", features = ["i128", "std"] }
Expand All @@ -48,7 +48,7 @@ scopeguard = "1.1.0"

[dependencies.ffmpeg]
package = "ffmpeg-next"
version = "4.4.0"
version = "6"
optional = true
default-features = false
features = ["codec", "format", "filter", "software-resampling", "software-scaling"]
Expand All @@ -58,7 +58,7 @@ features = ["codec", "format", "filter", "software-resampling", "software-scalin
# so all CLI dependencies have to be enabled by default
default = ["gifsicle", "binary"]
binary = ["dep:clap", "png", "pbr", "dep:wild", "dep:natord", "dep:dunce"]
png = ["lodepng"]
png = ["dep:lodepng"]
openmp = [] # deprecated, obsolete
openmp-static = [] # deprecated, obsolete
video = ["ffmpeg"]
Expand Down Expand Up @@ -88,4 +88,5 @@ strip = true
targets = ["x86_64-unknown-linux-gnu"]

[patch.crates-io]
ffmpeg-sys-next = { rev = "78458039d5fac99354f9cb078869f3be3f3af5fb", git = "https://github.com/kornelski/rust-ffmpeg-sys-1"}
# ffmpeg-sys-next does not support cross-compilation, which I use to produce binaries https://github.com/zmwangx/rust-ffmpeg-sys/pull/30
ffmpeg-sys-next = { rev = "94d5496d88900bdc0cad66733138134d0ea3cf31", git = "https://github.com/kornelski/rust-ffmpeg-sys-1"}
41 changes: 19 additions & 22 deletions gifski-api/snapcraft.yaml
Expand Up @@ -4,40 +4,37 @@ description: |
GIF encoder based on libimagequant (pngquant).
Squeezes maximum possible quality from the awful
GIF format. https://gif.ski
version: 1.11.0

adopt-info: gifski
base: core18
base: core20
confinement: strict

apps:
gifski:
command: gifski
command: bin/gifski
plugs:
- home
- removable-media

parts:
gifski:
after:
- selective-checkout

source: https://github.com/ImageOptim/gifski.git
override-pull: |
snapcraftctl pull
"$SNAPCRAFT_STAGE"/scriptlets/selective-checkout
plugin: rust
rust-features:
- openmp
stage-packages:
- libgomp1

selective-checkout:
plugin: nil
- video
build-packages:
- git
stage-snaps:
- selective-checkout
prime:
- -*
- pkg-config
- ffmpeg
- libavcodec-dev
- libavdevice-dev
- libavfilter-dev
- libavformat-dev
- libavresample-dev
- libavutil-dev
- libswresample-dev
- libswscale-dev
- libclang-dev
stage-packages:
- ffmpeg
- freeglut3 # dep leak from one of ffmpeg dev libs?
- libglu1-mesa
13 changes: 10 additions & 3 deletions gifski-api/src/bin/ffmpeg_source.rs
Expand Up @@ -45,7 +45,9 @@ impl FfmpegDecoder {
let filter_fps = self.rate.fps / self.rate.speed;
let stream = self.input_context.streams().best(ffmpeg::media::Type::Video).ok_or("The file has no video tracks")?;

let decoder = stream.codec().decoder().video().map_err(|e| format!("Unable to decode the codec used in the video: {}", e))?;
let mut codec_context = ffmpeg::codec::context::Context::new();
codec_context.set_parameters(stream.parameters())?;
let decoder = codec_context.decoder().video().map_err(|e| format!("Unable to decode the codec used in the video: {}", e))?;

let (dest_width, dest_height) = self.settings.dimensions_for_image(decoder.width() as _, decoder.height() as _);

Expand Down Expand Up @@ -103,8 +105,13 @@ impl FfmpegDecoder {
.chain(std::iter::once(ffmpeg::Packet::empty()));

for packet in packets {
let decoded = decoder.decode(&packet, &mut vid_frame)?;
if decoded {
decoder.send_packet(&packet)?;
loop {
match decoder.receive_frame(&mut vid_frame) {
Ok(()) => (),
Err(ffmpeg::Error::Other { errno: ffmpeg::error::EAGAIN }) | Err(ffmpeg::Error::Eof) => break,
Err(e) => return Err(Box::new(e)),
}
filter.get("in").ok_or("ffmpeg format error")?.source().add(&vid_frame)?;
let mut out = filter.get("out").ok_or("ffmpeg format error")?;
let mut out = out.sink();
Expand Down
4 changes: 2 additions & 2 deletions gifski-api/src/bin/gif.rs
Expand Up @@ -39,10 +39,10 @@ impl Source for GifDecoder {
while let Some(frame) = self.decoder.read_next_frame()? {
self.screen.blit_frame(frame)?;
let pixels = self.screen.pixels.clone();
let presentation_timestamp = f64::from(delay_ts) * (self.speed as f64 / 100.);
let presentation_timestamp = f64::from(delay_ts) * (f64::from(self.speed) / 100.);
c.add_frame_rgba(idx, pixels, presentation_timestamp)?;
idx += 1;
delay_ts += frame.delay as u32;
delay_ts += u32::from(frame.delay);
}
Ok(())
}
Expand Down
70 changes: 61 additions & 9 deletions gifski-api/src/bin/gifski.rs
@@ -1,5 +1,6 @@
use clap::builder::NonEmptyStringValueParser;
use std::io::Read;
use std::io::Stdout;
use gifski::{Settings, Repeat};
use clap::value_parser;

Expand All @@ -10,7 +11,7 @@ mod gif;
mod source;
use crate::source::Source;

use gifski::progress::{NoProgress, ProgressBar, ProgressReporter};
use gifski::progress::{NoProgress, ProgressReporter};

pub type BinResult<T, E = Box<dyn std::error::Error + Send + Sync>> = Result<T, E>;

Expand Down Expand Up @@ -230,12 +231,7 @@ fn bin_main() -> BinResult<()> {
let progress: &mut dyn ProgressReporter = if quiet {
&mut nopb
} else {
pb = ProgressBar::new(decoder.total_frames().unwrap_or(100));
pb.show_speed = false;
pb.show_percent = false;
pb.format(" #_. ");
pb.message("Frame ");
pb.set_max_refresh_rate(Some(Duration::from_millis(250)));
pb = ProgressBar::new(decoder.total_frames());
&mut pb
};

Expand All @@ -252,6 +248,7 @@ fn bin_main() -> BinResult<()> {
#[allow(deprecated)]
writer.set_lossy_quality(lossy_quality);
}

let decode_thread = thread::Builder::new().name("decode".into()).spawn(move || {
decoder.collect(&mut collector)
})?;
Expand Down Expand Up @@ -303,11 +300,12 @@ fn check_if_paths_exist(paths: &[PathBuf]) -> BinResult<()> {
if !path.exists() {
let mut msg = format!("Unable to find the input file: \"{}\"", path.display());
if path.to_str().map_or(false, |p| p.contains('*')) {
msg += "\nThe path contains a literal \"*\" character. Either no files matched the pattern, or the pattern was in quotes.";
msg += "\nThe \"*\" character was in quotes, which disabled pattern matching. The file with an actual literal asterisk in its name obviously doesn't exist.\nTo search for all files matching a pattern, use * without quotes.";
} else if path.extension() == Some("gif".as_ref()) {
msg = format!("Did you mean to use -o \"{}\" to specify it as the output file instead?", path.display());
} else if path.is_relative() {
msg += &format!(" (searched in \"{}\")", env::current_dir()?.display());
use std::fmt::Write;
write!(&mut msg, " (searched in \"{}\")", env::current_dir()?.display())?;
}
return Err(msg.into())
}
Expand Down Expand Up @@ -362,3 +360,57 @@ Alternatively, use ffmpeg command to export PNG frames, and then specify
the PNG files as input for this executable. Instructions on https://gif.ski
".into())
}

struct ProgressBar {
pb: pbr::ProgressBar<Stdout>,
frames: u64,
total: Option<u64>,
previous_estimate: u64,
displayed_estimate: u64,
}
impl ProgressBar {
fn new(total: Option<u64>) -> Self {
let mut pb = pbr::ProgressBar::new(total.unwrap_or(100));
pb.show_speed = false;
pb.show_percent = false;
pb.format(" #_. ");
pb.message("Frame ");
pb.set_max_refresh_rate(Some(Duration::from_millis(250)));
Self {
pb, frames: 0, total, previous_estimate: 0, displayed_estimate: 0,
}
}
}

impl ProgressReporter for ProgressBar {
fn increase(&mut self) -> bool {
self.frames += 1;
if self.total.is_none() {
self.pb.total = (self.frames + 50).max(100);
}
self.pb.inc();
true
}

fn written_bytes(&mut self, bytes: u64) {
let min_frames = self.total.map(|t| (t / 16).clamp(5, 50)).unwrap_or(10);
if self.frames > min_frames {
let total_size = bytes * self.pb.total / self.frames;
let new_estimate = if total_size >= self.previous_estimate { total_size } else { (self.previous_estimate + total_size) / 2 };
self.previous_estimate = new_estimate;
if self.displayed_estimate.abs_diff(new_estimate) > new_estimate/10 {
self.displayed_estimate = new_estimate;
let (num, unit, x) = if new_estimate > 1_000_000 {
(new_estimate as f64/1_000_000., "MB", if new_estimate > 10_000_000 {0} else {1})
} else {
(new_estimate as f64/1_000., "KB", 0)
};
self.pb.message(&format!("{num:.x$}{unit} GIF; Frame "));
}
}
}

fn done(&mut self, msg: &str) {
self.pb.finish_print(msg);
}
}

0 comments on commit 052e4ab

Please sign in to comment.