Skip to content

Commit

Permalink
Update vapi files (#650)
Browse files Browse the repository at this point in the history
Copied in the changes from the Shotwell vapi files and fixed up and tested the associated vala.

All of these code changes relate to importing and previewing files from a camera which I've just tested still works with my Pixel 4a.

I also changed how the image-missing icon is loaded in the import page as this was not working and causing criticals in the terminal.

This should get us some memory leak fixes as we've now got rid of a lot of the manual memory management that wasn't done right anyway.
  • Loading branch information
davidmhewitt authored Oct 1, 2021
1 parent 016ca92 commit bc7feca
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 175 deletions.
50 changes: 27 additions & 23 deletions src/Views/ImportPage.vala
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,7 @@ class ImportPreview : CheckerboardItem {
bool using_placeholder = (pixbuf == null);
if (pixbuf == null) {
if (placeholder_preview == null) {
placeholder_preview = AppWindow.get_instance ().render_icon ("image-missing",
Gtk.IconSize.DIALOG, null);
placeholder_preview = get_placeholder_pixbuf ();
placeholder_preview = scale_pixbuf (placeholder_preview, MAX_SCALE,
Gdk.InterpType.BILINEAR, true);
}
Expand All @@ -305,6 +304,21 @@ class ImportPreview : CheckerboardItem {
set_image (pixbuf);
}

private Gdk.Pixbuf get_placeholder_pixbuf () {
Gdk.Pixbuf? pixbuf = null;

try {
var icon_theme = Gtk.IconTheme.get_default ();
pixbuf = icon_theme.load_icon ("image-missing", 48, 0);
} catch (Error e) {
// Create an empty black pixbuf as a fallback
pixbuf = new Gdk.Pixbuf (Gdk.Colorspace.RGB, false, 8, 48, 48);
pixbuf.fill (0);
}

return pixbuf;
}

public bool is_already_imported () {
PhotoImportSource photo_import_source = get_import_source () as PhotoImportSource;
if (photo_import_source != null) {
Expand Down Expand Up @@ -1250,11 +1264,10 @@ public class ImportPage : CheckerboardPage {

Gee.ArrayList<ImportSource> import_list = new Gee.ArrayList<ImportSource> ();

GPhoto.CameraStorageInformation *sifs = null;
int count = 0;
refresh_result = camera.get_storageinfo (&sifs, out count, spin_idle_context.context);
GPhoto.CameraStorageInformation[] sifs = null;
refresh_result = camera.get_storageinfo (out sifs, spin_idle_context.context);
if (refresh_result == GPhoto.Result.OK) {
for (int fsid = 0; fsid < count; fsid++) {
for (int fsid = 0; fsid < sifs.length; fsid++) {
// Check well-known video and image paths first to prevent accidental
// scanning of undesired directories (which can cause user annoyance with
// some smartphones or camera-equipped media players)
Expand Down Expand Up @@ -1389,18 +1402,15 @@ public class ImportPage : CheckerboardPage {
// Need to do this because some phones (iPhone, in particular) changes the name of their filesystem
// between each mount
public static string? get_fs_basedir (GPhoto.Camera camera, int fsid) {
GPhoto.CameraStorageInformation *sifs = null;
int count = 0;
GPhoto.Result res = camera.get_storageinfo (&sifs, out count, null_context.context);
GPhoto.CameraStorageInformation[] sifs = null;
GPhoto.Result res = camera.get_storageinfo (out sifs, null_context.context);
if (res != GPhoto.Result.OK)
return null;

if (fsid >= count)
if (fsid >= sifs.length)
return null;

GPhoto.CameraStorageInformation *ifs = sifs + fsid;

return (ifs->fields & GPhoto.CameraStorageInfoFields.BASE) != 0 ? ifs->basedir : "/";
return (sifs[fsid].fields & GPhoto.CameraStorageInfoFields.BASE) != 0 ? (string)sifs[fsid].basedir : "/";
}

public static string? get_fulldir (GPhoto.Camera camera, string camera_name, int fsid, string folder) {
Expand Down Expand Up @@ -1474,12 +1484,12 @@ public class ImportPage : CheckerboardPage {
import_list.add (video_source);
} else {
// determine file format from type, and then from file extension
PhotoFileFormat file_format = PhotoFileFormat.from_gphoto_type (info.file.type);
PhotoFileFormat file_format = PhotoFileFormat.from_gphoto_type ((string)info.file.type);
if (file_format == PhotoFileFormat.UNKNOWN) {
file_format = PhotoFileFormat.get_by_basename_extension (filename);
if (file_format == PhotoFileFormat.UNKNOWN) {
message ("Skipping %s/%s: Not a supported file extension (%s)", fulldir,
filename, info.file.type);
filename, (string)info.file.type);

continue;
}
Expand Down Expand Up @@ -1627,9 +1637,8 @@ public class ImportPage : CheckerboardPage {
// this means the preview orientation will be wrong and the MD5 is not generated
// if the EXIF did not parse properly (see above)

uint8[] preview_raw = null;
size_t preview_raw_length = 0;
Gdk.Pixbuf preview = null;
string? preview_md5 = null;
try {
string preview_fulldir = fulldir;
string preview_filename = filename;
Expand All @@ -1638,7 +1647,7 @@ public class ImportPage : CheckerboardPage {
preview_filename = associated.filename;
}
preview = GPhoto.load_preview (spin_idle_context.context, camera, preview_fulldir,
preview_filename, out preview_raw, out preview_raw_length);
preview_filename, out preview_md5);
} catch (Error err) {
// only issue the warning message if we're not reading a video. GPhoto is capable
// of reading video previews about 50% of the time, so we don't want to put a guard
Expand All @@ -1650,11 +1659,6 @@ public class ImportPage : CheckerboardPage {
}
}

// calculate thumbnail fingerprint
string? preview_md5 = null;
if (preview != null && preview_raw != null && preview_raw_length > 0)
preview_md5 = md5_binary (preview_raw, preview_raw_length);

#if TRACE_MD5
debug ("camera MD5 %s: exif=%s preview=%s", filename, exif_only_md5, preview_md5);
#endif
Expand Down
93 changes: 36 additions & 57 deletions src/camera/GPhoto.vala
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,24 @@ public bool get_info (Context context, Camera camera, string folder, string file
return true;
}

public Bytes? camera_file_to_bytes (Context context, CameraFile file) {
// if buffer can be loaded into memory, return a Bytes class with
// CameraFile being the owner of the data. This way, the CameraFile is freed
// when the Bytes are freed
unowned uint8[] buffer = null;
var res = file.get_data (out buffer);
if (res != Result.OK)
return null;

return Bytes.new_with_owner<GPhoto.CameraFile> (buffer, file);
}

// Libgphoto will in some instances refuse to get metadata from a camera, but the camera is accessable as a
// filesystem. In these cases shotwell can access the file directly. See:
// http://redmine.yorba.org/issues/2959
public PhotoMetadata? get_fallback_metadata (Camera camera, Context context, string folder, string filename) {
GPhoto.CameraStorageInformation *sifs = null;
int count = 0;
camera.get_storageinfo (&sifs, out count, context);
GPhoto.CameraStorageInformation[] sifs = null;
camera.get_storageinfo (out sifs, context);

GPhoto.PortInfo port_info;
camera.get_port_info (out port_info);
Expand All @@ -158,9 +169,10 @@ public PhotoMetadata? get_fallback_metadata (Camera camera, Context context, str
}

public Gdk.Pixbuf? load_preview (Context context, Camera camera, string folder, string filename,
out uint8[] raw, out size_t raw_length) throws Error {
raw = null;
raw_length = 0;
out string? preview_md5) throws Error {
Bytes? raw = null;
Bytes? out_bytes = null;
preview_md5 = null;

try {
raw = load_file_into_buffer (context, camera, folder, filename, GPhoto.CameraFileType.PREVIEW);
Expand All @@ -170,35 +182,22 @@ public Gdk.Pixbuf? load_preview (Context context, Camera camera, string folder,
return null;
if (0 == metadata.get_preview_count ())
return null;
PhotoPreview? preview = metadata.get_preview (metadata.get_preview_count () - 1);

// Get the smallest preview from meta-data
var preview = metadata.get_preview (metadata.get_preview_count () - 1);
raw = preview.flatten ();
preview_md5 = Checksum.compute_for_bytes (ChecksumType.MD5, raw);
}

if (raw == null) {
raw_length = 0;
return null;
}
out_bytes = raw;
preview_md5 = Checksum.compute_for_bytes (ChecksumType.MD5, out_bytes);

raw_length = raw.length;

// Try to make sure last two bytes are JPEG footer.
// This is necessary because GPhoto sometimes includes a few extra bytes. See
// Yorba bug #2905 and the following GPhoto bug:
// http://sourceforge.net/tracker/?func=detail&aid=3141521&group_id=8874&atid=108874
if (raw_length > 32) {
for (size_t i = raw_length - 2; i > raw_length - 32; i--) {
if (raw[i] == Jpeg.MARKER_PREFIX && raw[i + 1] == Jpeg.Marker.EOI) {
debug ("Adjusted length of thumbnail for: %s", filename);
raw_length = i + 2;
break;
}
}
}
MemoryInputStream mins = new MemoryInputStream.from_bytes (raw);

MemoryInputStream mins = new MemoryInputStream.from_data (raw, null);
return new Gdk.Pixbuf.from_stream_at_scale (mins, ImportPreview.MAX_SCALE, ImportPreview.MAX_SCALE, true, null);
}


public Gdk.Pixbuf? load_image (Context context, Camera camera, string folder, string filename)
throws Error {
InputStream ins = load_file_into_stream (context, camera, folder, filename, GPhoto.CameraFileType.NORMAL);
Expand Down Expand Up @@ -228,7 +227,7 @@ public void save_image (Context context, Camera camera, string folder, string fi

public PhotoMetadata? load_metadata (Context context, Camera camera, string folder, string filename)
throws Error {
uint8[] camera_raw = null;
Bytes? camera_raw = null;
try {
camera_raw = load_file_into_buffer (context, camera, folder, filename, GPhoto.CameraFileType.EXIF);
} catch {
Expand Down Expand Up @@ -258,17 +257,12 @@ public InputStream load_file_into_stream (Context context, Camera camera, string
throw new GPhotoError.LIBRARY ("[%d] Error retrieving file object for %s/%s: %s",
(int) res, folder, filename, res.as_string ());

// if entire file fits in memory, return a stream from that ... can't merely wrap
// MemoryInputStream around the camera_file buffer, as that will be destroyed when the
// function returns
unowned uint8 *data;
ulong data_len;
res = camera_file.get_data_and_size (out data, out data_len);
if (res == Result.OK) {
uint8[] buffer = new uint8[data_len];
Memory.copy (buffer, data, buffer.length);

return new MemoryInputStream.from_data (buffer, on_mins_destroyed);
// if entire file fits in memory, return a stream from that ...
// The camera_file is set as data on the object to keep it alive while
// the MemoryInputStream is alive.
var bytes = camera_file_to_bytes (context, camera_file);
if (bytes != null) {
return new MemoryInputStream.from_bytes (bytes);
}

// if not stored in memory, try copying it to a temp file and then reading out of that
Expand All @@ -281,13 +275,9 @@ public InputStream load_file_into_stream (Context context, Camera camera, string
return temp.read (null);
}

private static void on_mins_destroyed (void *data) {
free (data);
}

// Returns a buffer with the requested file, if within reason. Use load_file for larger files.
public uint8[]? load_file_into_buffer (Context context, Camera camera, string folder,
string filename, CameraFileType filetype) throws Error {
public Bytes? load_file_into_buffer (Context context, Camera camera, string folder,
string filename, CameraFileType filetype) throws Error {
GPhoto.CameraFile camera_file;
GPhoto.Result res = GPhoto.CameraFile.create (out camera_file);
if (res != Result.OK)
Expand All @@ -298,17 +288,6 @@ public uint8[]? load_file_into_buffer (Context context, Camera camera, string fo
throw new GPhotoError.LIBRARY ("[%d] Error retrieving file object for %s/%s: %s",
(int) res, folder, filename, res.as_string ());

// if buffer can be loaded into memory, return a copy of that (can't return buffer itself
// as it will be destroyed when the camera_file is unref'd)
unowned uint8 *data;
ulong data_len;
res = camera_file.get_data_and_size (out data, out data_len);
if (res != Result.OK)
return null;

uint8[] buffer = new uint8[data_len];
Memory.copy (buffer, data, buffer.length);

return buffer;
return camera_file_to_bytes (context, camera_file);
}
}
34 changes: 10 additions & 24 deletions src/photos/PhotoMetadata.vala
Original file line number Diff line number Diff line change
Expand Up @@ -91,16 +91,15 @@ public abstract class PhotoPreview {
return extension;
}

public abstract uint8[] flatten () throws Error;
public abstract Bytes flatten () throws Error;

public virtual Gdk.Pixbuf? get_pixbuf () throws Error {
uint8[] flattened = flatten ();
var flattened = flatten ();

// Need to create from stream or file for decode ... catch decode error and return null,
// different from an I/O error causing the problem
try {
return new Gdk.Pixbuf.from_stream (new MemoryInputStream.from_data (flattened, null),
null);
return new Gdk.Pixbuf.from_stream (new MemoryInputStream.from_bytes (flattened));
} catch (Error err) {
warning ("Unable to decode thumbnail for %s: %s", name, err.message);

Expand Down Expand Up @@ -137,11 +136,11 @@ public class PhotoMetadata : MediaMetadata {
this.number = number;
}

public override uint8[] flatten () throws Error {
public override Bytes flatten () throws Error {
unowned GExiv2.PreviewProperties?[] props = owner.exiv2.get_preview_properties ();
assert (props != null && props.length > number);

return owner.exiv2.get_preview_image (props[number]).get_data ();
return new Bytes (owner.exiv2.get_preview_image (props[number]).get_data ());
}
}

Expand Down Expand Up @@ -174,31 +173,18 @@ public class PhotoMetadata : MediaMetadata {
exiv2 = new GExiv2.Metadata ();
exif = null;

#if GEXIV2_0_11
exiv2.open_buf (buffer[0:length]);
#else
exiv2.open_buf (buffer, length);
#endif
exif = Exif.Data.new_from_data (buffer, length);
exif = Exif.Data.new_from_data (buffer[0:length]);
source_name = "<memory buffer %d bytes>".printf (length);
}

public void read_from_app1_segment (uint8[] buffer, int length = 0) throws Error {
if (length <= 0)
length = buffer.length;

assert (buffer.length >= length);

public void read_from_app1_segment (Bytes buffer) throws Error {
exiv2 = new GExiv2.Metadata ();
exif = null;

#if GEXIV2_0_11
exiv2.from_app1_segment (buffer[0:length]);
#else
exiv2.from_app1_segment (buffer, length);
#endif
exif = Exif.Data.new_from_data (buffer, length);
source_name = "<app1 segment %d bytes>".printf (length);
exiv2.from_app1_segment (buffer.get_data ());
exif = Exif.Data.new_from_data (buffer.get_data ());
source_name = "<app1 segment %zu bytes>".printf (buffer.get_size ());
}

public static MetadataDomain get_tag_domain (string tag) {
Expand Down
Loading

0 comments on commit bc7feca

Please sign in to comment.