diff --git a/photos/salmon.avi b/photos/salmon.avi new file mode 100644 index 0000000..348b456 Binary files /dev/null and b/photos/salmon.avi differ diff --git a/src/main/java/org/forkalsrud/album/exif/DirectoryEntry.java b/src/main/java/org/forkalsrud/album/exif/DirectoryEntry.java index ab1e9a4..ad35b99 100644 --- a/src/main/java/org/forkalsrud/album/exif/DirectoryEntry.java +++ b/src/main/java/org/forkalsrud/album/exif/DirectoryEntry.java @@ -112,7 +112,7 @@ public class DirectoryEntry extends EntryWithChildren { return props; } - DirectoryProps generateCache() throws IOException { + DirectoryProps generateCache() throws IOException, InterruptedException { DirectoryProps props = new DirectoryMetadataGenerator(file).generate(); db.store(file.getAbsolutePath(), props); diff --git a/src/main/java/org/forkalsrud/album/exif/DirectoryMetadataGenerator.java b/src/main/java/org/forkalsrud/album/exif/DirectoryMetadataGenerator.java index 5173e5c..8ccb5c2 100644 --- a/src/main/java/org/forkalsrud/album/exif/DirectoryMetadataGenerator.java +++ b/src/main/java/org/forkalsrud/album/exif/DirectoryMetadataGenerator.java @@ -3,9 +3,12 @@ package org.forkalsrud.album.exif; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; import java.text.DecimalFormat; import java.text.NumberFormat; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Iterator; @@ -44,7 +47,7 @@ public class DirectoryMetadataGenerator { } - public DirectoryProps generate() throws IOException { + public DirectoryProps generate() throws IOException, InterruptedException { long start = System.currentTimeMillis(); DirectoryProps props = new DirectoryProps(); @@ -56,7 +59,7 @@ public class DirectoryMetadataGenerator { } - private void generateFileEntries(Properties props) throws IOException { + private void generateFileEntries(Properties props) throws IOException, InterruptedException { File[] files = dir.listFiles(); for (File f : files) { @@ -80,16 +83,30 @@ public class DirectoryMetadataGenerator { continue; } if (name.endsWith(".jpg") || name.endsWith(".jpeg") || name.endsWith(".JPG")) { - String base = "file." + f.getName() + "."; Map p = generateThumbnailProperties(f); - for (Map.Entry e : p.entrySet()) { - props.setProperty(base + e.getKey(), e.getValue()); - } + addPropsForFile(props, f, p); + } else if (name.endsWith(".MOV") || name.endsWith(".mp4") || name.endsWith(".avi")) { + Map p = generateVideoProperties(f); + addPropsForFile(props, f, p); } } } + /** + * Adds properties by prefixing each with "file." plus the file name + * @param props all props + * @param f file (to get the name) + * @param p the props to add + */ + private void addPropsForFile(Properties props, File f, Map p) { + String base = "file." + f.getName() + "."; + for (Map.Entry e : p.entrySet()) { + props.setProperty(base + e.getKey(), e.getValue()); + } + } + + private void generateDirectoryProperties(Properties props, File f) { String name = f.getName(); @@ -167,20 +184,82 @@ public class DirectoryMetadataGenerator { return props; } + + /** + * The crazy Mac OSX does not even set the PATH to a reasonable value, so + * we have to jump through hoops to guess where we may find the executables + * for mplayer and friends. + * + * @param name + * @return + * @throws IOException + * @throws InterruptedException + */ + private String findExecutableInShellPath(String name) throws IOException, InterruptedException { + + String executableForName = name; + ProcessBuilder pb = new ProcessBuilder(Arrays.asList(System.getenv("SHELL"))); + pb.redirectErrorStream(true); // send errors to stdout + Process p = pb.start(); + PrintStream stdin = new PrintStream(p.getOutputStream()); + stdin.print("echo $PATH"); // This is still not entirely portable. Windows would like %PATH% + stdin.close(); + InputStream stdout = p.getInputStream(); + String searchPath = IOUtils.toString(stdout); + int returnStatus = p.waitFor(); + + String separator = System.getProperty("path.separator"); + if (searchPath != null && separator != null && !"".equals(separator)) { + String[] elements = searchPath.split(separator); + for (String path : elements) { + + File executable = new File(path, name); + if (executable.isFile()) { + executableForName = executable.getAbsolutePath(); + break; + } + } + } + return executableForName; + } + + + /* + * Transcoding probably needs to go in at some point. + * A suitable command line may be like the one below, as suggested by + * http://rob.opendot.cl/index.php/useful-stuff/ffmpeg-x264-encoding-guide/ + * http://rob.opendot.cl/index.php/useful-stuff/encoding-a-flv-video-for-embedded-web-playback/ + * + * Assuming video in is 1280x720 pixels resolution and we want to show 640x360 + * and we want an output bitrate about 1 Mbit/sec + * we can do a one pass encoding like this: + * + * ffmpeg -i -aspect 1280:720 -s 640x360 -b 1000000 -crf 25 \ + * -vcodec libx264 -vpre knut_low \ + * -acodec libfaac -aq 100 \ + * .mp4 + */ + + /** * - * @param f the jpeg file + * @param f the movie file * @return a map with the following keys: orientation dimensions captureDate comment etag * @throws IOException + * @throws InterruptedException */ - private Map generateVideoProperties(File f) throws IOException { + private Map generateVideoProperties(File f) throws IOException, InterruptedException { + + String mplayerExecutable = findExecutableInShellPath("mplayer"); + Map props = new HashMap(); ProcessBuilder pb = new ProcessBuilder().command( - "mplayer", "-vo", "null", "-ao", "null", "-frames", "0", "-identify", f.getAbsolutePath()); + mplayerExecutable, "-vo", "null", "-ao", "null", "-frames", "0", "-identify", f.getAbsolutePath()); pb.redirectErrorStream(false); Process p = pb.start(); p.getOutputStream().close(); List lines = IOUtils.readLines(p.getInputStream()); + int returnStatus = p.waitFor(); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss"); String width = "", height = ""; for (String line : lines) { @@ -192,6 +271,7 @@ public class DirectoryMetadataGenerator { if (name.equals("ID_VIDEO_HEIGHT")) height = value; } } + props.put("type", "movie"); props.put("orientation", "1"); props.put("dimensions", new Dimension(width, height).toString()); props.put("captureDate", sdf.format(new Date(f.lastModified())));