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,
|
||||
exif: &ExifData,
|
||||
) -> 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(
|
||||
db,
|
||||
file_path,
|
||||
width as i32,
|
||||
height as i32,
|
||||
exif.date(),
|
||||
exif.rotation()?,
|
||||
find_camera(db, exif)?,
|
||||
|
@ -3,15 +3,10 @@ use diesel::pg::PgConnection;
|
||||
use diesel::prelude::*;
|
||||
use diesel::result::Error as DieselError;
|
||||
use diesel::update;
|
||||
use models::{Modification, Photo};
|
||||
use photosdir::PhotosDir;
|
||||
use models::Photo;
|
||||
use std::io::prelude::*;
|
||||
|
||||
pub fn one(
|
||||
db: &PgConnection,
|
||||
photodir: &PhotosDir,
|
||||
tpath: &str,
|
||||
) -> Result<(), Error> {
|
||||
pub fn one(db: &PgConnection, tpath: &str) -> Result<(), Error> {
|
||||
use schema::photos::dsl::*;
|
||||
match update(photos.filter(path.eq(&tpath)))
|
||||
.set(is_public.eq(true))
|
||||
@ -22,15 +17,7 @@ pub fn one(
|
||||
Ok(())
|
||||
}
|
||||
Err(DieselError::NotFound) => {
|
||||
if !photodir.has_file(&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::Other(format!("File {} is not known", tpath)))
|
||||
}
|
||||
Err(error) => Err(error.into()),
|
||||
}
|
||||
@ -38,26 +25,10 @@ pub fn one(
|
||||
|
||||
pub fn by_file_list<In: BufRead + Sized>(
|
||||
db: &PgConnection,
|
||||
photodir: &PhotosDir,
|
||||
list: In,
|
||||
) -> Result<(), Error> {
|
||||
for line in list.lines() {
|
||||
one(db, photodir, &line?)?;
|
||||
one(db, &line?)?;
|
||||
}
|
||||
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(())
|
||||
}
|
||||
("makepublic", Some(args)) => {
|
||||
let pd = PhotosDir::new(photos_dir());
|
||||
let db = get_db()?;
|
||||
match args.value_of("LIST") {
|
||||
Some("-") => {
|
||||
let list = io::stdin();
|
||||
makepublic::by_file_list(&db, &pd, list.lock())?;
|
||||
makepublic::by_file_list(&db, list.lock())?;
|
||||
Ok(())
|
||||
}
|
||||
Some(f) => {
|
||||
let list = File::open(f)?;
|
||||
let list = BufReader::new(list);
|
||||
makepublic::by_file_list(&db, &pd, list)
|
||||
}
|
||||
None => {
|
||||
makepublic::one(&db, &pd, args.value_of("IMAGE").unwrap())
|
||||
makepublic::by_file_list(&db, list)
|
||||
}
|
||||
None => makepublic::one(&db, args.value_of("IMAGE").unwrap()),
|
||||
}
|
||||
}
|
||||
("stats", Some(_args)) => show_stats(&get_db()?),
|
||||
|
@ -4,6 +4,7 @@ use diesel::pg::PgConnection;
|
||||
use diesel::prelude::*;
|
||||
use diesel::result::Error as DieselError;
|
||||
use server::SizeTag;
|
||||
use std::cmp::max;
|
||||
|
||||
#[derive(AsChangeset, Clone, Debug, Identifiable, Queryable)]
|
||||
pub struct Photo {
|
||||
@ -15,6 +16,8 @@ pub struct Photo {
|
||||
pub is_public: bool,
|
||||
pub camera_id: Option<i32>,
|
||||
pub attribution_id: Option<i32>,
|
||||
pub width: Option<i32>,
|
||||
pub height: Option<i32>,
|
||||
}
|
||||
|
||||
use schema::photos;
|
||||
@ -55,6 +58,8 @@ impl Photo {
|
||||
pub fn update_by_path(
|
||||
db: &PgConnection,
|
||||
file_path: &str,
|
||||
newwidth: i32,
|
||||
newheight: i32,
|
||||
exifdate: Option<NaiveDateTime>,
|
||||
exifrotation: i16,
|
||||
camera: &Option<Camera>,
|
||||
@ -69,6 +74,12 @@ impl Photo {
|
||||
{
|
||||
let mut change = false;
|
||||
// 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 {
|
||||
change = true;
|
||||
pic = diesel::update(photos.find(pic.id))
|
||||
@ -102,6 +113,8 @@ impl Photo {
|
||||
pub fn create_or_set_basics(
|
||||
db: &PgConnection,
|
||||
file_path: &str,
|
||||
newwidth: i32,
|
||||
newheight: i32,
|
||||
exifdate: Option<NaiveDateTime>,
|
||||
exifrotation: i16,
|
||||
camera: Option<Camera>,
|
||||
@ -112,6 +125,8 @@ impl Photo {
|
||||
if let Some(result) = Self::update_by_path(
|
||||
db,
|
||||
file_path,
|
||||
newwidth,
|
||||
newheight,
|
||||
exifdate,
|
||||
exifrotation,
|
||||
&camera,
|
||||
@ -123,6 +138,8 @@ impl Photo {
|
||||
path.eq(file_path),
|
||||
date.eq(exifdate),
|
||||
rotation.eq(exifrotation),
|
||||
width.eq(newwidth),
|
||||
height.eq(newheight),
|
||||
camera_id.eq(camera.map(|c| c.id)),
|
||||
))
|
||||
.get_result::<Photo>(db)?;
|
||||
@ -193,6 +210,19 @@ impl Photo {
|
||||
use schema::cameras::dsl::cameras;
|
||||
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)]
|
||||
|
@ -14,8 +14,8 @@ pub struct ExifData {
|
||||
gpstime: Option<(u8, u8, u8)>,
|
||||
make: Option<String>,
|
||||
model: Option<String>,
|
||||
width: Option<u32>,
|
||||
height: Option<u32>,
|
||||
pub width: Option<u32>,
|
||||
pub height: Option<u32>,
|
||||
orientation: Option<u32>,
|
||||
latval: Option<f64>,
|
||||
longval: Option<f64>,
|
||||
|
@ -37,6 +37,8 @@ pub struct PhotoLink {
|
||||
pub title: Option<String>,
|
||||
pub href: String,
|
||||
pub id: i32,
|
||||
// Size should not be optional, but make it best-effort for now.
|
||||
pub size: Option<(u32, u32)>,
|
||||
pub lable: Option<String>,
|
||||
}
|
||||
|
||||
@ -48,6 +50,7 @@ impl PhotoLink {
|
||||
fn imgscore(p: &Photo) -> i16 {
|
||||
p.grade.unwrap_or(27) + if p.is_public { 38 } else { 0 }
|
||||
}
|
||||
let photo = g.iter().max_by_key(|p| imgscore(p)).unwrap();
|
||||
PhotoLink {
|
||||
title: None,
|
||||
href: format!(
|
||||
@ -56,10 +59,8 @@ impl PhotoLink {
|
||||
g.last().map(|p| p.id).unwrap_or(0),
|
||||
g.first().map(|p| p.id).unwrap_or(0),
|
||||
),
|
||||
id: g.iter()
|
||||
.max_by_key(|p| imgscore(p))
|
||||
.map(|p| p.id)
|
||||
.unwrap_or(0),
|
||||
id: photo.id,
|
||||
size: photo.get_size(SizeTag::Small.px()),
|
||||
lable: {
|
||||
let from = g.last().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,
|
||||
href: format!("/img/{}", p.id),
|
||||
id: p.id,
|
||||
size: p.get_size(SizeTag::Small.px()),
|
||||
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 chrono::Duration as ChDuration;
|
||||
use chrono::naive::{NaiveDate, NaiveDateTime};
|
||||
@ -46,6 +46,7 @@ pub fn all_years<'mw>(
|
||||
} else {
|
||||
q.filter(date.is_null())
|
||||
};
|
||||
let photo = photo.first::<Photo>(c).unwrap();
|
||||
PhotoLink {
|
||||
title: Some(
|
||||
year.map(|y| format!("{}", y))
|
||||
@ -53,7 +54,8 @@ pub fn all_years<'mw>(
|
||||
),
|
||||
href: format!("/{}/", year.unwrap_or(0)),
|
||||
lable: Some(format!("{} images", count)),
|
||||
id: photo.first::<Photo>(c).unwrap().id,
|
||||
id: photo.id,
|
||||
size: photo.get_size(SizeTag::Small.px()),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@ -105,6 +107,7 @@ pub fn months_in_year<'mw>(
|
||||
href: format!("/{}/{}/", year, month),
|
||||
lable: Some(format!("{} pictures", count)),
|
||||
id: photo.id,
|
||||
size: photo.get_size(SizeTag::Small.px()),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@ -158,6 +161,7 @@ pub fn days_in_month<'mw>(
|
||||
href: format!("/{}/{}/{}", year, month, day),
|
||||
lable: Some(format!("{} pictures", count)),
|
||||
id: photo.id,
|
||||
size: photo.get_size(SizeTag::Small.px()),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@ -278,6 +282,7 @@ pub fn on_this_day<'mw>(
|
||||
href: format!("/{}/{}/{}", year, month, day),
|
||||
lable: Some(format!("{} pictures", count)),
|
||||
id: photo.id,
|
||||
size: photo.get_size(SizeTag::Small.px()),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
|
@ -2,6 +2,6 @@
|
||||
|
||||
@(photo: &PhotoLink)
|
||||
<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>}
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user