Skip to content

Commit fa11903

Browse files
committed
Initial commit
0 parents  commit fa11903

File tree

7 files changed

+244
-0
lines changed

7 files changed

+244
-0
lines changed

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Generated by Cargo
2+
# will have compiled files and executables
3+
/target/
4+
5+
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6+
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
7+
Cargo.lock
8+
9+
# These are backup files generated by rustfmt
10+
**/*.rs.bk

Cargo.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "lambda-image-resize-rust"
3+
version = "0.1.0"
4+
authors = ["Robert Beekman <[email protected]>"]
5+
6+
[dependencies]
7+
lambda_runtime = "0.1"
8+
serde = "^1"
9+
serde_json = "^1"
10+
serde_derive = "*"
11+
log = "^0.4"
12+
simple_logger = "^1"
13+
aws_lambda_events = "^0.2.0"
14+
rust-s3 = "0.10.0"
15+
image= "*"
16+
rayon = "1.0"
17+
18+
[[bin]]
19+
name = "bootstrap"
20+
path = "src/main.rs"

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2019 Robert Beekman
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

Makefile

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
build:
2+
docker run --rm \
3+
-v ${PWD}:/code \
4+
-v ${HOME}/.cargo/registry:/root/.cargo/registry \
5+
-v ${HOME}/.cargo/git:/root/.cargo/git \
6+
softprops/lambda-rust
7+
8+
rust_version:
9+
rustup override add 1.32.0

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Lambda Image Resize
2+
3+
A simple image resize lambda function, written in Rust.
4+
5+
This binary responds to Amazon S3 events and triggers a resize on the uploaded image with the sizes specified. Right now you can only resize on the width of an image.
6+
7+
## Configure
8+
9+
This binary relies on two env vars:
10+
11+
* `SIZES`, an array if sizes (`export SIZES=200,300,400`)
12+
* `REPLACEMENTS`, an array of replacements for the path (`export REPLACEMENTS="original:resized"`)
13+
14+
## Compile
15+
16+
Use [Lambda-Rust docker image](https://hub.docker.com/r/softprops/lambda-rust/) to compile this binary. With Docker running run the following command to build a release.
17+
18+
```
19+
make build
20+
```
21+
22+
You can find the (zipped) bootstrap ready for upload to AWS Lambda in `target/lambda/release/bootstrap.zip`

src/config.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use std::env;
2+
3+
#[derive(Debug, Clone, Deserialize)]
4+
pub struct Config {
5+
pub sizes: Vec<f32>,
6+
pub replacements: Vec<(String, String)>,
7+
}
8+
9+
impl Config {
10+
pub fn new() -> Config {
11+
Config {
12+
sizes: Config::parse_sizes(),
13+
replacements: Config::parse_replacements(),
14+
}
15+
}
16+
17+
pub fn parse_sizes() -> Vec<f32> {
18+
let mut sizes = Vec::new();
19+
if let Ok(sizes_string) = env::var("SIZES") {
20+
for size_string in sizes_string.split(',').into_iter() {
21+
sizes.push(size_string.parse().unwrap());
22+
}
23+
};
24+
25+
sizes
26+
}
27+
28+
pub fn parse_replacements() -> Vec<(String, String)> {
29+
let mut replacements = Vec::new();
30+
if let Ok(replacements_string) = env::var("REPLACEMENTS") {
31+
for replacement in replacements_string.split(',').into_iter() {
32+
let key_val: Vec<&str> = replacement.splitn(2, ":").collect();
33+
replacements.push((key_val[0].to_string(), key_val[1].to_string()));
34+
}
35+
};
36+
37+
replacements
38+
}
39+
}

src/main.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
extern crate aws_lambda_events;
2+
extern crate image;
3+
#[macro_use]
4+
extern crate lambda_runtime as lambda;
5+
#[macro_use]
6+
extern crate log;
7+
extern crate rayon;
8+
extern crate s3;
9+
#[macro_use]
10+
extern crate serde_derive;
11+
extern crate serde_json;
12+
extern crate simple_logger;
13+
14+
use image::{GenericImageView, ImageError, JPEG};
15+
use rayon::prelude::*;
16+
use s3::bucket::Bucket;
17+
use s3::credentials::Credentials;
18+
use s3::region::Region;
19+
20+
mod config;
21+
22+
use aws_lambda_events::event::s3::{S3Event, S3EventRecord};
23+
use config::Config;
24+
use lambda::error::HandlerError;
25+
use serde_json::Value;
26+
use std::error::Error;
27+
28+
fn main() -> Result<(), Box<Error>> {
29+
simple_logger::init_with_level(log::Level::Info)?;
30+
31+
lambda!(handle_event);
32+
33+
Ok(())
34+
}
35+
36+
fn handle_event(event: Value, ctx: lambda::Context) -> Result<(), HandlerError> {
37+
let config = Config::new();
38+
39+
let s3_event: S3Event =
40+
serde_json::from_value(event).map_err(|e| ctx.new_error(e.to_string().as_str()))?;
41+
42+
for record in s3_event.records {
43+
handle_record(&config, record);
44+
}
45+
Ok(())
46+
}
47+
48+
fn handle_record(config: &Config, record: S3EventRecord) {
49+
let credentials = Credentials::default();
50+
let region: Region = record
51+
.aws_region
52+
.expect("Could not get region from record")
53+
.parse()
54+
.expect("Could not parse region from record");
55+
let bucket = Bucket::new(
56+
&record
57+
.s3
58+
.bucket
59+
.name
60+
.expect("Could not get bucket name from record"),
61+
region,
62+
credentials,
63+
);
64+
let source = record
65+
.s3
66+
.object
67+
.key
68+
.expect("Could not get key from object record");
69+
info!("Fetching: {}, config: {:?}", &source, &config);
70+
71+
/* Make sure we don't process files twice */
72+
for size in &config.sizes {
73+
let to_match = format!("-{}.jpg", size);
74+
if source.ends_with(&to_match) {
75+
warn!(
76+
"Source: '{}' ends with: '{}'. Skipping.",
77+
&source,
78+
&to_match
79+
);
80+
return;
81+
}
82+
}
83+
84+
let (data, _) = bucket
85+
.get(&source)
86+
.expect(&format!("Could not get object: {}", &source));
87+
88+
let img = image::load_from_memory(&data)
89+
.ok()
90+
.expect("Opening image failed");
91+
92+
let _: Vec<_> = config
93+
.sizes
94+
.par_iter()
95+
.map(|size| {
96+
let buffer = resize_image(&img, &size).expect("Could not resize image");
97+
98+
let mut target = source.clone();
99+
for (rep_key, rep_val) in &config.replacements {
100+
target = target.replace(rep_key, rep_val);
101+
}
102+
target = target.replace(".jpg", &format!("-{}.jpg", size));
103+
let (_, code) = bucket
104+
.put(&target, &buffer, "image/jpeg")
105+
.expect(&format!("Could not upload object to :{}", &target));
106+
info!("Uploaded: {} with: {}", &target, &code);
107+
})
108+
.collect();
109+
}
110+
111+
fn resize_image(img: &image::DynamicImage, new_w: &f32) -> Result<Vec<u8>, ImageError> {
112+
let mut result: Vec<u8> = Vec::new();
113+
114+
let old_w = img.width() as f32;
115+
let old_h = img.height() as f32;
116+
let ratio = new_w / old_w;
117+
let new_h = (old_h * ratio).floor();
118+
119+
let scaled = img.resize(*new_w as u32, new_h as u32, image::FilterType::Lanczos3);
120+
scaled.write_to(&mut result, JPEG)?;
121+
122+
Ok(result)
123+
}

0 commit comments

Comments
 (0)