Store width and height of images.
Use it to calculate width and height of small images in lists. Since the old database should be migratable to the new, the width and height fields are nullable. I aim to make them not nullable later, after all images in the database has got width and height.
This commit is contained in:
parent
c2388befa9
commit
fc1ed81561
2
migrations/2018-03-03-142213_photo_size/down.sql
Normal file
2
migrations/2018-03-03-142213_photo_size/down.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE photos DROP COLUMN width;
|
||||||
|
ALTER TABLE photos DROP COLUMN height;
|
6
migrations/2018-03-03-142213_photo_size/up.sql
Normal file
6
migrations/2018-03-03-142213_photo_size/up.sql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
-- Add width and height to photos
|
||||||
|
-- Intially make them nullable, to make it possible to apply the
|
||||||
|
-- migration to an existing database. A NOT NULL constraint should
|
||||||
|
-- be added later, when all photos has sizes.
|
||||||
|
ALTER TABLE photos ADD COLUMN width INTEGER;
|
||||||
|
ALTER TABLE photos ADD COLUMN height INTEGER;
|
@ -27,9 +27,15 @@ fn save_photo(
|
|||||||
file_path: &str,
|
file_path: &str,
|
||||||
exif: &ExifData,
|
exif: &ExifData,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
let width = exif.width
|
||||||
|
.ok_or(Error::Other(format!("Image {} missing width", file_path)))?;
|
||||||
|
let height = exif.height
|
||||||
|
.ok_or(Error::Other(format!("Image {} missing height", file_path)))?;
|
||||||
let photo = match Photo::create_or_set_basics(
|
let photo = match Photo::create_or_set_basics(
|
||||||
db,
|
db,
|
||||||
file_path,
|
file_path,
|
||||||
|
width as i32,
|
||||||
|
height as i32,
|
||||||
exif.date(),
|
exif.date(),
|
||||||
exif.rotation()?,
|
exif.rotation()?,
|
||||||
find_camera(db, exif)?,
|
find_camera(db, exif)?,
|
||||||
|
@ -3,15 +3,10 @@ use diesel::pg::PgConnection;
|
|||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::result::Error as DieselError;
|
use diesel::result::Error as DieselError;
|
||||||
use diesel::update;
|
use diesel::update;
|
||||||
use models::{Modification, Photo};
|
use models::Photo;
|
||||||
use photosdir::PhotosDir;
|
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
|
||||||
pub fn one(
|
pub fn one(db: &PgConnection, tpath: &str) -> Result<(), Error> {
|
||||||
db: &PgConnection,
|
|
||||||
photodir: &PhotosDir,
|
|
||||||
tpath: &str,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
use schema::photos::dsl::*;
|
use schema::photos::dsl::*;
|
||||||
match update(photos.filter(path.eq(&tpath)))
|
match update(photos.filter(path.eq(&tpath)))
|
||||||
.set(is_public.eq(true))
|
.set(is_public.eq(true))
|
||||||
@ -22,15 +17,7 @@ pub fn one(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Err(DieselError::NotFound) => {
|
Err(DieselError::NotFound) => {
|
||||||
if !photodir.has_file(&tpath) {
|
Err(Error::Other(format!("File {} is not known", tpath)))
|
||||||
return Err(Error::Other(format!(
|
|
||||||
"File {} does not exist",
|
|
||||||
tpath,
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
let photo = register_photo(db, tpath)?;
|
|
||||||
println!("New photo {:?} is public.", photo);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
Err(error) => Err(error.into()),
|
Err(error) => Err(error.into()),
|
||||||
}
|
}
|
||||||
@ -38,26 +25,10 @@ pub fn one(
|
|||||||
|
|
||||||
pub fn by_file_list<In: BufRead + Sized>(
|
pub fn by_file_list<In: BufRead + Sized>(
|
||||||
db: &PgConnection,
|
db: &PgConnection,
|
||||||
photodir: &PhotosDir,
|
|
||||||
list: In,
|
list: In,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
for line in list.lines() {
|
for line in list.lines() {
|
||||||
one(db, photodir, &line?)?;
|
one(db, &line?)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn register_photo(
|
|
||||||
db: &PgConnection,
|
|
||||||
tpath: &str,
|
|
||||||
) -> Result<Photo, DieselError> {
|
|
||||||
use schema::photos::dsl::{is_public, photos};
|
|
||||||
let photo = match Photo::create_or_set_basics(db, tpath, None, 0, None)? {
|
|
||||||
Modification::Created(photo)
|
|
||||||
| Modification::Updated(photo)
|
|
||||||
| Modification::Unchanged(photo) => photo,
|
|
||||||
};
|
|
||||||
update(photos.find(photo.id))
|
|
||||||
.set(is_public.eq(true))
|
|
||||||
.get_result::<Photo>(db)
|
|
||||||
}
|
|
||||||
|
@ -169,22 +169,19 @@ fn run(args: &ArgMatches) -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
("makepublic", Some(args)) => {
|
("makepublic", Some(args)) => {
|
||||||
let pd = PhotosDir::new(photos_dir());
|
|
||||||
let db = get_db()?;
|
let db = get_db()?;
|
||||||
match args.value_of("LIST") {
|
match args.value_of("LIST") {
|
||||||
Some("-") => {
|
Some("-") => {
|
||||||
let list = io::stdin();
|
let list = io::stdin();
|
||||||
makepublic::by_file_list(&db, &pd, list.lock())?;
|
makepublic::by_file_list(&db, list.lock())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Some(f) => {
|
Some(f) => {
|
||||||
let list = File::open(f)?;
|
let list = File::open(f)?;
|
||||||
let list = BufReader::new(list);
|
let list = BufReader::new(list);
|
||||||
makepublic::by_file_list(&db, &pd, list)
|
makepublic::by_file_list(&db, list)
|
||||||
}
|
|
||||||
None => {
|
|
||||||
makepublic::one(&db, &pd, args.value_of("IMAGE").unwrap())
|
|
||||||
}
|
}
|
||||||
|
None => makepublic::one(&db, args.value_of("IMAGE").unwrap()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
("stats", Some(_args)) => show_stats(&get_db()?),
|
("stats", Some(_args)) => show_stats(&get_db()?),
|
||||||
|
@ -4,6 +4,7 @@ use diesel::pg::PgConnection;
|
|||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::result::Error as DieselError;
|
use diesel::result::Error as DieselError;
|
||||||
use server::SizeTag;
|
use server::SizeTag;
|
||||||
|
use std::cmp::max;
|
||||||
|
|
||||||
#[derive(AsChangeset, Clone, Debug, Identifiable, Queryable)]
|
#[derive(AsChangeset, Clone, Debug, Identifiable, Queryable)]
|
||||||
pub struct Photo {
|
pub struct Photo {
|
||||||
@ -15,6 +16,8 @@ pub struct Photo {
|
|||||||
pub is_public: bool,
|
pub is_public: bool,
|
||||||
pub camera_id: Option<i32>,
|
pub camera_id: Option<i32>,
|
||||||
pub attribution_id: Option<i32>,
|
pub attribution_id: Option<i32>,
|
||||||
|
pub width: Option<i32>,
|
||||||
|
pub height: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
use schema::photos;
|
use schema::photos;
|
||||||
@ -55,6 +58,8 @@ impl Photo {
|
|||||||
pub fn update_by_path(
|
pub fn update_by_path(
|
||||||
db: &PgConnection,
|
db: &PgConnection,
|
||||||
file_path: &str,
|
file_path: &str,
|
||||||
|
newwidth: i32,
|
||||||
|
newheight: i32,
|
||||||
exifdate: Option<NaiveDateTime>,
|
exifdate: Option<NaiveDateTime>,
|
||||||
exifrotation: i16,
|
exifrotation: i16,
|
||||||
camera: &Option<Camera>,
|
camera: &Option<Camera>,
|
||||||
@ -69,6 +74,12 @@ impl Photo {
|
|||||||
{
|
{
|
||||||
let mut change = false;
|
let mut change = false;
|
||||||
// TODO Merge updates to one update statement!
|
// TODO Merge updates to one update statement!
|
||||||
|
if pic.width != Some(newwidth) || pic.height != Some(newheight) {
|
||||||
|
change = true;
|
||||||
|
pic = diesel::update(photos.find(pic.id))
|
||||||
|
.set((width.eq(newwidth), height.eq(newheight)))
|
||||||
|
.get_result::<Photo>(db)?;
|
||||||
|
}
|
||||||
if exifdate.is_some() && exifdate != pic.date {
|
if exifdate.is_some() && exifdate != pic.date {
|
||||||
change = true;
|
change = true;
|
||||||
pic = diesel::update(photos.find(pic.id))
|
pic = diesel::update(photos.find(pic.id))
|
||||||
@ -102,6 +113,8 @@ impl Photo {
|
|||||||
pub fn create_or_set_basics(
|
pub fn create_or_set_basics(
|
||||||
db: &PgConnection,
|
db: &PgConnection,
|
||||||
file_path: &str,
|
file_path: &str,
|
||||||
|
newwidth: i32,
|
||||||
|
newheight: i32,
|
||||||
exifdate: Option<NaiveDateTime>,
|
exifdate: Option<NaiveDateTime>,
|
||||||
exifrotation: i16,
|
exifrotation: i16,
|
||||||
camera: Option<Camera>,
|
camera: Option<Camera>,
|
||||||
@ -112,6 +125,8 @@ impl Photo {
|
|||||||
if let Some(result) = Self::update_by_path(
|
if let Some(result) = Self::update_by_path(
|
||||||
db,
|
db,
|
||||||
file_path,
|
file_path,
|
||||||
|
newwidth,
|
||||||
|
newheight,
|
||||||
exifdate,
|
exifdate,
|
||||||
exifrotation,
|
exifrotation,
|
||||||
&camera,
|
&camera,
|
||||||
@ -123,6 +138,8 @@ impl Photo {
|
|||||||
path.eq(file_path),
|
path.eq(file_path),
|
||||||
date.eq(exifdate),
|
date.eq(exifdate),
|
||||||
rotation.eq(exifrotation),
|
rotation.eq(exifrotation),
|
||||||
|
width.eq(newwidth),
|
||||||
|
height.eq(newheight),
|
||||||
camera_id.eq(camera.map(|c| c.id)),
|
camera_id.eq(camera.map(|c| c.id)),
|
||||||
))
|
))
|
||||||
.get_result::<Photo>(db)?;
|
.get_result::<Photo>(db)?;
|
||||||
@ -193,6 +210,19 @@ impl Photo {
|
|||||||
use schema::cameras::dsl::cameras;
|
use schema::cameras::dsl::cameras;
|
||||||
self.camera_id.and_then(|i| cameras.find(i).first(db).ok())
|
self.camera_id.and_then(|i| cameras.find(i).first(db).ok())
|
||||||
}
|
}
|
||||||
|
pub fn get_size(&self, max_size: u32) -> Option<(u32, u32)> {
|
||||||
|
if let (Some(width), Some(height)) = (self.width, self.height) {
|
||||||
|
let scale = f64::from(max_size) / f64::from(max(width, height));
|
||||||
|
let w = (scale * f64::from(width)) as u32;
|
||||||
|
let h = (scale * f64::from(height)) as u32;
|
||||||
|
match self.rotation {
|
||||||
|
_x @ 0...44 | _x @ 315...360 | _x @ 135...224 => Some((w, h)),
|
||||||
|
_ => Some((h, w)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Queryable)]
|
#[derive(Debug, Clone, Queryable)]
|
||||||
|
@ -14,8 +14,8 @@ pub struct ExifData {
|
|||||||
gpstime: Option<(u8, u8, u8)>,
|
gpstime: Option<(u8, u8, u8)>,
|
||||||
make: Option<String>,
|
make: Option<String>,
|
||||||
model: Option<String>,
|
model: Option<String>,
|
||||||
width: Option<u32>,
|
pub width: Option<u32>,
|
||||||
height: Option<u32>,
|
pub height: Option<u32>,
|
||||||
orientation: Option<u32>,
|
orientation: Option<u32>,
|
||||||
latval: Option<f64>,
|
latval: Option<f64>,
|
||||||
longval: Option<f64>,
|
longval: Option<f64>,
|
||||||
|
@ -37,6 +37,8 @@ pub struct PhotoLink {
|
|||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
pub href: String,
|
pub href: String,
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
|
// Size should not be optional, but make it best-effort for now.
|
||||||
|
pub size: Option<(u32, u32)>,
|
||||||
pub lable: Option<String>,
|
pub lable: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +50,7 @@ impl PhotoLink {
|
|||||||
fn imgscore(p: &Photo) -> i16 {
|
fn imgscore(p: &Photo) -> i16 {
|
||||||
p.grade.unwrap_or(27) + if p.is_public { 38 } else { 0 }
|
p.grade.unwrap_or(27) + if p.is_public { 38 } else { 0 }
|
||||||
}
|
}
|
||||||
|
let photo = g.iter().max_by_key(|p| imgscore(p)).unwrap();
|
||||||
PhotoLink {
|
PhotoLink {
|
||||||
title: None,
|
title: None,
|
||||||
href: format!(
|
href: format!(
|
||||||
@ -56,10 +59,8 @@ impl PhotoLink {
|
|||||||
g.last().map(|p| p.id).unwrap_or(0),
|
g.last().map(|p| p.id).unwrap_or(0),
|
||||||
g.first().map(|p| p.id).unwrap_or(0),
|
g.first().map(|p| p.id).unwrap_or(0),
|
||||||
),
|
),
|
||||||
id: g.iter()
|
id: photo.id,
|
||||||
.max_by_key(|p| imgscore(p))
|
size: photo.get_size(SizeTag::Small.px()),
|
||||||
.map(|p| p.id)
|
|
||||||
.unwrap_or(0),
|
|
||||||
lable: {
|
lable: {
|
||||||
let from = g.last().and_then(|p| p.date);
|
let from = g.last().and_then(|p| p.date);
|
||||||
let to = g.first().and_then(|p| p.date);
|
let to = g.first().and_then(|p| p.date);
|
||||||
@ -101,6 +102,7 @@ impl<'a> From<&'a Photo> for PhotoLink {
|
|||||||
title: None,
|
title: None,
|
||||||
href: format!("/img/{}", p.id),
|
href: format!("/img/{}", p.id),
|
||||||
id: p.id,
|
id: p.id,
|
||||||
|
size: p.get_size(SizeTag::Small.px()),
|
||||||
lable: p.date.map(|d| format!("{}", d.format("%F %T"))),
|
lable: p.date.map(|d| format!("{}", d.format("%F %T"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use super::{Link, PhotoLink};
|
use super::{Link, PhotoLink, SizeTag};
|
||||||
use super::splitlist::links_by_time;
|
use super::splitlist::links_by_time;
|
||||||
use chrono::Duration as ChDuration;
|
use chrono::Duration as ChDuration;
|
||||||
use chrono::naive::{NaiveDate, NaiveDateTime};
|
use chrono::naive::{NaiveDate, NaiveDateTime};
|
||||||
@ -46,6 +46,7 @@ pub fn all_years<'mw>(
|
|||||||
} else {
|
} else {
|
||||||
q.filter(date.is_null())
|
q.filter(date.is_null())
|
||||||
};
|
};
|
||||||
|
let photo = photo.first::<Photo>(c).unwrap();
|
||||||
PhotoLink {
|
PhotoLink {
|
||||||
title: Some(
|
title: Some(
|
||||||
year.map(|y| format!("{}", y))
|
year.map(|y| format!("{}", y))
|
||||||
@ -53,7 +54,8 @@ pub fn all_years<'mw>(
|
|||||||
),
|
),
|
||||||
href: format!("/{}/", year.unwrap_or(0)),
|
href: format!("/{}/", year.unwrap_or(0)),
|
||||||
lable: Some(format!("{} images", count)),
|
lable: Some(format!("{} images", count)),
|
||||||
id: photo.first::<Photo>(c).unwrap().id,
|
id: photo.id,
|
||||||
|
size: photo.get_size(SizeTag::Small.px()),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@ -105,6 +107,7 @@ pub fn months_in_year<'mw>(
|
|||||||
href: format!("/{}/{}/", year, month),
|
href: format!("/{}/{}/", year, month),
|
||||||
lable: Some(format!("{} pictures", count)),
|
lable: Some(format!("{} pictures", count)),
|
||||||
id: photo.id,
|
id: photo.id,
|
||||||
|
size: photo.get_size(SizeTag::Small.px()),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@ -158,6 +161,7 @@ pub fn days_in_month<'mw>(
|
|||||||
href: format!("/{}/{}/{}", year, month, day),
|
href: format!("/{}/{}/{}", year, month, day),
|
||||||
lable: Some(format!("{} pictures", count)),
|
lable: Some(format!("{} pictures", count)),
|
||||||
id: photo.id,
|
id: photo.id,
|
||||||
|
size: photo.get_size(SizeTag::Small.px()),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@ -278,6 +282,7 @@ pub fn on_this_day<'mw>(
|
|||||||
href: format!("/{}/{}/{}", year, month, day),
|
href: format!("/{}/{}/{}", year, month, day),
|
||||||
lable: Some(format!("{} pictures", count)),
|
lable: Some(format!("{} pictures", count)),
|
||||||
id: photo.id,
|
id: photo.id,
|
||||||
|
size: photo.get_size(SizeTag::Small.px()),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
|
@ -2,6 +2,6 @@
|
|||||||
|
|
||||||
@(photo: &PhotoLink)
|
@(photo: &PhotoLink)
|
||||||
<div class="item">@if let Some(ref title) = photo.title {<h2>@title</h2>}
|
<div class="item">@if let Some(ref title) = photo.title {<h2>@title</h2>}
|
||||||
<a href="@photo.href"><img src="/img/@photo.id-s.jpg"></a>
|
<a href="@photo.href"><img src="/img/@photo.id-s.jpg" @if let Some(s) = photo.size {width="@s.0" height="@s.1"} alt="Photo @photo.id"></a>
|
||||||
@if let Some(ref d) = photo.lable {<span class="lable">@d</span>}
|
@if let Some(ref d) = photo.lable {<span class="lable">@d</span>}
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user