2016-05-04 14:16:20 +03:00
|
|
|
#[macro_use]
|
|
|
|
extern crate log;
|
2016-01-31 16:34:48 +03:00
|
|
|
extern crate chrono;
|
|
|
|
extern crate env_logger;
|
|
|
|
extern crate image;
|
|
|
|
extern crate rexif;
|
|
|
|
extern crate rustc_serialize;
|
2016-05-29 22:51:08 +03:00
|
|
|
extern crate dotenv;
|
|
|
|
extern crate diesel;
|
|
|
|
extern crate rphotos;
|
2016-01-31 16:34:48 +03:00
|
|
|
|
|
|
|
use chrono::format::ParseError;
|
|
|
|
use chrono::naive::datetime::NaiveDateTime;
|
|
|
|
use rexif::{ExifData, ExifEntry, ExifTag, TagValue};
|
|
|
|
use std::path::Path;
|
2016-05-29 22:51:08 +03:00
|
|
|
use dotenv::dotenv;
|
|
|
|
use diesel::pg::PgConnection;
|
|
|
|
use self::diesel::prelude::*;
|
2016-08-09 02:41:42 +03:00
|
|
|
use rphotos::models::{Modification, Photo, Camera};
|
2016-01-31 16:34:48 +03:00
|
|
|
|
|
|
|
mod env;
|
|
|
|
use env::{dburl, photos_dir};
|
|
|
|
mod photosdir;
|
|
|
|
use photosdir::PhotosDir;
|
|
|
|
|
|
|
|
|
|
|
|
fn main() {
|
2016-05-29 22:51:08 +03:00
|
|
|
dotenv().ok();
|
2016-01-31 16:34:48 +03:00
|
|
|
env_logger::init().unwrap();
|
2016-05-29 22:51:08 +03:00
|
|
|
let db = PgConnection::establish(&dburl())
|
2016-07-24 23:37:09 +03:00
|
|
|
.expect("Error connecting to database");
|
2016-01-31 16:34:48 +03:00
|
|
|
let photos = PhotosDir::new(photos_dir());
|
|
|
|
|
2016-06-05 22:58:01 +03:00
|
|
|
let args = std::env::args().skip(1);
|
|
|
|
if args.len() > 0 {
|
|
|
|
for a in args {
|
|
|
|
do_find(&db, &photos, Path::new(&a));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
println!("No args");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn do_find(db: &PgConnection, photos: &PhotosDir, only_in: &Path) {
|
2016-05-04 14:16:20 +03:00
|
|
|
photos.find_files(only_in,
|
|
|
|
&|path, exif| {
|
2016-05-29 22:51:08 +03:00
|
|
|
match save_photo(&db, path, &exif) {
|
2016-05-04 14:16:20 +03:00
|
|
|
Ok(()) => debug!("Saved photo {}", path),
|
|
|
|
Err(e) => {
|
|
|
|
warn!("Failed to save photo {}: {:?}",
|
|
|
|
path,
|
|
|
|
e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2016-07-24 23:37:09 +03:00
|
|
|
.unwrap();
|
2016-01-31 16:34:48 +03:00
|
|
|
}
|
|
|
|
|
2016-05-29 22:51:08 +03:00
|
|
|
fn save_photo(db: &PgConnection,
|
|
|
|
file_path: &str,
|
2016-05-04 14:16:20 +03:00
|
|
|
exif: &ExifData)
|
2016-07-24 23:37:09 +03:00
|
|
|
-> FindPhotoResult<()> {
|
|
|
|
let photo = match try!(Photo::create_or_set_basics(db, file_path,
|
2016-08-09 02:41:42 +03:00
|
|
|
Some(try!(find_date(&exif))),
|
|
|
|
try!(find_rotation(&exif)),
|
|
|
|
try!(find_camera(db, exif)))) {
|
2016-07-24 23:37:09 +03:00
|
|
|
Modification::Created(photo) => {
|
|
|
|
info!("Created {:?}", photo);
|
|
|
|
photo
|
|
|
|
}
|
|
|
|
Modification::Updated(photo) => {
|
|
|
|
info!("Modified {:?}", photo);
|
|
|
|
photo
|
|
|
|
}
|
|
|
|
Modification::Unchanged(photo) => {
|
|
|
|
debug!("No change for {:?}", photo);
|
|
|
|
photo
|
|
|
|
}
|
|
|
|
};
|
2016-07-09 17:04:19 +03:00
|
|
|
if let Some((lat, long)) = try!(find_position(&exif)) {
|
|
|
|
debug!("Position for {} is {} {}", file_path, lat, long);
|
|
|
|
use rphotos::schema::positions::dsl::*;
|
|
|
|
if let Ok((pos, clat, clong)) =
|
2016-07-24 23:37:09 +03:00
|
|
|
positions.filter(photo_id.eq(photo.id))
|
|
|
|
.select((id, latitude, longitude))
|
|
|
|
.first::<(i32, i32, i32)>(db) {
|
2016-07-09 17:04:19 +03:00
|
|
|
if (clat != (lat * 1e6) as i32) || (clong != (long * 1e6) as i32) {
|
|
|
|
panic!("TODO Should update position #{} from {} {} to {} {}",
|
|
|
|
pos, clat, clong, lat, long)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
info!("Position for {} is {} {}", file_path, lat, long);
|
|
|
|
use rphotos::models::NewPosition;
|
|
|
|
diesel::insert(&NewPosition {
|
|
|
|
photo_id: photo.id,
|
|
|
|
latitude: (lat * 1e6) as i32,
|
2016-07-24 23:37:09 +03:00
|
|
|
longitude: (long * 1e6) as i32,
|
|
|
|
})
|
|
|
|
.into(positions)
|
|
|
|
.execute(db)
|
|
|
|
.expect("Insert image position");
|
2016-07-09 17:04:19 +03:00
|
|
|
}
|
|
|
|
}
|
2016-01-31 16:34:48 +03:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2016-08-09 02:41:42 +03:00
|
|
|
fn find_camera(db: &PgConnection, exif: &ExifData) -> FindPhotoResult<Option<Camera>> {
|
|
|
|
if let (Some(maketag), Some(modeltag)) = (find_entry(exif, &ExifTag::Make),
|
|
|
|
find_entry(exif, &ExifTag::Model)) {
|
|
|
|
if let (TagValue::Ascii(make), TagValue::Ascii(model)) =
|
|
|
|
(maketag.clone().value, modeltag.clone().value) {
|
|
|
|
let cam = try!(Camera::get_or_create(db, &make, &model));
|
|
|
|
return Ok(Some(cam));
|
|
|
|
}
|
|
|
|
// TODO else Err(...?)
|
|
|
|
}
|
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
|
2016-07-24 23:37:09 +03:00
|
|
|
type FindPhotoResult<T> = Result<T, FindPhotoError>;
|
|
|
|
|
2016-01-31 16:34:48 +03:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum FindPhotoError {
|
2016-05-29 22:51:08 +03:00
|
|
|
DatabaseError(diesel::result::Error),
|
2016-01-31 16:34:48 +03:00
|
|
|
ExifOfUnexpectedType(TagValue),
|
|
|
|
ExifTagMissing(ExifTag),
|
|
|
|
TimeFormat(ParseError),
|
|
|
|
UnknownOrientation(u16),
|
2016-07-24 23:37:09 +03:00
|
|
|
BadLatLong(TagValue),
|
2016-01-31 16:34:48 +03:00
|
|
|
}
|
2016-05-29 22:51:08 +03:00
|
|
|
impl From<diesel::result::Error> for FindPhotoError {
|
|
|
|
fn from(err: diesel::result::Error) -> FindPhotoError {
|
|
|
|
FindPhotoError::DatabaseError(err)
|
|
|
|
}
|
|
|
|
}
|
2016-01-31 16:34:48 +03:00
|
|
|
impl From<ParseError> for FindPhotoError {
|
|
|
|
fn from(err: ParseError) -> FindPhotoError {
|
|
|
|
FindPhotoError::TimeFormat(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-24 23:37:09 +03:00
|
|
|
fn find_rotation(exif: &ExifData) -> FindPhotoResult<i16> {
|
2016-01-31 16:34:48 +03:00
|
|
|
if let Some(ref value) = find_entry(exif, &ExifTag::Orientation) {
|
|
|
|
if let TagValue::U16(ref v) = value.value {
|
|
|
|
let n = v[0];
|
2016-05-29 22:51:08 +03:00
|
|
|
debug!("Raw orientation is {}", n);
|
2016-01-31 16:34:48 +03:00
|
|
|
match n {
|
|
|
|
1 => Ok(0),
|
|
|
|
3 => Ok(180),
|
|
|
|
6 => Ok(90),
|
|
|
|
8 => Ok(270),
|
|
|
|
x => Err(FindPhotoError::UnknownOrientation(x)),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Err(FindPhotoError::ExifOfUnexpectedType(value.value.clone()))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
info!("Orientation tag missing, default to 0 degrees");
|
|
|
|
Ok(0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-24 23:37:09 +03:00
|
|
|
fn find_date(exif: &ExifData) -> FindPhotoResult<NaiveDateTime> {
|
2016-07-29 12:19:23 +03:00
|
|
|
find_entry(exif, &ExifTag::DateTimeOriginal)
|
2016-08-06 00:21:24 +03:00
|
|
|
.or_else(|| find_entry(exif, &ExifTag::DateTime))
|
|
|
|
.or_else(|| find_entry(exif, &ExifTag::DateTimeDigitized))
|
2016-07-29 12:19:23 +03:00
|
|
|
.map(|value| {
|
|
|
|
debug!("Found {:?}", value);
|
|
|
|
if let TagValue::Ascii(ref str) = value.value {
|
|
|
|
debug!("Try to parse {:?} (from {:?}) as datetime",
|
|
|
|
str, value.tag);
|
|
|
|
Ok(try!(NaiveDateTime::parse_from_str(str, "%Y:%m:%d %T")))
|
|
|
|
} else {
|
|
|
|
Err(FindPhotoError::ExifOfUnexpectedType(value.value.clone()))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.unwrap_or_else(|| {
|
|
|
|
Err(FindPhotoError::ExifTagMissing(ExifTag::DateTimeOriginal))
|
|
|
|
})
|
2016-01-31 16:34:48 +03:00
|
|
|
}
|
|
|
|
|
2016-07-24 23:37:09 +03:00
|
|
|
fn find_position(exif: &ExifData) -> FindPhotoResult<Option<(f64, f64)>> {
|
|
|
|
if let Some(lat) = find_entry(exif, &ExifTag::GPSLatitude) {
|
|
|
|
if let Some(long) = find_entry(exif, &ExifTag::GPSLongitude) {
|
|
|
|
return Ok(Some((try!(rat2float(&lat.value)),
|
|
|
|
try!(rat2float(&long.value)))));
|
|
|
|
}
|
2016-07-09 17:04:19 +03:00
|
|
|
}
|
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
|
2016-07-24 23:37:09 +03:00
|
|
|
fn rat2float(val: &rexif::TagValue) -> FindPhotoResult<f64> {
|
2016-07-09 17:04:19 +03:00
|
|
|
if let rexif::TagValue::URational(ref v) = *val {
|
2016-07-24 23:37:09 +03:00
|
|
|
if v.len() == 3 {
|
|
|
|
return Ok(v[0].value() +
|
|
|
|
(v[1].value() + v[2].value() / 60.0) / 60.0);
|
|
|
|
}
|
2016-07-09 17:04:19 +03:00
|
|
|
}
|
2016-07-24 23:37:09 +03:00
|
|
|
Err(FindPhotoError::BadLatLong(val.clone()))
|
2016-07-09 17:04:19 +03:00
|
|
|
}
|
|
|
|
|
2016-01-31 16:34:48 +03:00
|
|
|
fn find_entry<'a>(exif: &'a ExifData, tag: &ExifTag) -> Option<&'a ExifEntry> {
|
|
|
|
for entry in &exif.entries {
|
|
|
|
if entry.tag == *tag {
|
|
|
|
return Some(entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|