Compare commits

...

10 commits

Author SHA1 Message Date
9dd452fe36 Use <video> element for videos
Somewhat rough attempt at making this work.  Doesn't do a good
job with matching file extensions or setting mime types for different
falvors of video.  No longer attempts transcoding video to the right
resolution.
2025-11-29 15:32:02 -08:00
23fb5e4c68 Allow search results 2025-02-04 17:11:13 -08:00
efdd2b6ea9 jpeg2000 and Jbig support 2025-02-04 17:11:13 -08:00
958c02e670 Clean up various IntelliJ warnings 2025-02-02 16:41:47 -08:00
7df5a06cd1 Clean up various IntelliJ warnings 2025-02-01 18:47:18 -08:00
05859a2e58 Clean up various IntelliJ warnings 2025-02-01 18:16:13 -08:00
3f94629953 Allow filenames with # in them
Avoid unnecessary nullpointer exception
2024-12-30 15:13:09 -08:00
4e662b92ff unused code + linting 2024-11-02 17:01:50 -07:00
2b8f7e474a Integrate search funtion in UI 2024-09-16 17:11:10 -07:00
da75204ded UI improvements
Channeling more through ng.html, less through
velocity templates.
2024-03-18 20:46:06 -07:00
17 changed files with 1460 additions and 1386 deletions

View file

@ -16,8 +16,8 @@
Create a file: `~/forkalsrud.org/photo.properties` and for each named "root"
(`photo` in the example below), point the `base` property and `dbdir` properties
to the direcory holding the photos, and the diractory to be populated with
BerkelyDB files to your photo folder:
to the directory holding the photos, and the directory to be populated with
BerkeleyDB files to your photo folder:
root.photo.base=/home/photo
root.photo.dbdir=/home/resin/web/db

18
pom.xml
View file

@ -119,7 +119,7 @@
<dependency>
<groupId>com.drewnoakes</groupId>
<artifactId>metadata-extractor</artifactId>
<version>2.18.0</version>
<version>2.19.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
@ -247,6 +247,22 @@
<artifactId>pdfbox-io</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>jbig2-imageio</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>com.github.jai-imageio</groupId>
<artifactId>jai-imageio-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>com.github.jai-imageio</groupId>
<artifactId>jai-imageio-jpeg2000</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>

View file

@ -93,7 +93,9 @@ public class ThumbnailDatabase extends TupleBinding<CachedImage> {
img.mimeType = in.readString();
int lobLen = in.readInt();
img.bits = new byte[lobLen];
in.read(img.bits, 0, lobLen);
if (lobLen > 0) {
in.read(img.bits, 0, lobLen);
}
return img;
}
@ -104,8 +106,12 @@ public class ThumbnailDatabase extends TupleBinding<CachedImage> {
out.writeInt(1); // version 1
out.writeLong(img.lastModified);
out.writeString(img.mimeType);
out.writeInt(img.bits.length);
out.write(img.bits);
if (img.bits != null) {
out.writeInt(img.bits.length);
out.write(img.bits);
} else {
out.writeInt(0);
}
}
}

View file

@ -367,7 +367,7 @@ public class Editor
int findRegion(int ex, int ey) {
Rectangle r = roi.getScreenRectangle();
int c = 8;
int c;
if (within(ex, r.x + r.width)) {
if (within(ey, r.y + r.height)) {

View file

@ -26,7 +26,14 @@ public class Dimension {
}
public Dimension(String s) {
String[] coords = s.split("x");
String[] coords;
if (s.contains("x")) {
coords = s.split("x");
} else if (s.contains(" ")) {
coords = s.split(" ");
} else {
throw new RuntimeException("Bad dimension: " + s);
}
this.w = Integer.parseInt(coords[0]);
this.h = Integer.parseInt(coords[1]);
}

View file

@ -8,11 +8,8 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.forkalsrud.album.db.DirectoryDatabase;
@ -27,14 +24,14 @@ public class DirectoryEntry extends EntryWithChildren<Entry> {
public interface ServiceApi {
public DirectoryDatabase getDirectoryDatabase();
public DirectoryMetadataGenerator getMetadataGenerator();
DirectoryDatabase getDirectoryDatabase();
DirectoryMetadataGenerator getMetadataGenerator();
}
ServiceApi services;
DirectoryProps cache;
boolean childrenLoaded = false;
Comparator<Entry> sort = null;
Comparator<Entry> ordering = null;
Date earliest = null;
boolean groupByYear;
@ -129,7 +126,7 @@ public class DirectoryEntry extends EntryWithChildren<Entry> {
private void sort() {
Collections.sort(children, sort);
children.sort(ordering);
}
@ -139,57 +136,45 @@ public class DirectoryEntry extends EntryWithChildren<Entry> {
String coverFileName = props.getProperty("cover");
String caption = props.getProperty("caption");
setCaption(caption);
sort = ComparatorFactory.getSort(props.getProperty("sort"));
ordering = ComparatorFactory.getSort(props.getProperty("sort"));
HashMap<String, Entry> entryMap = new HashMap<String, Entry>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss");
Date oldest = new Date(file.lastModified());
Iterator<Object> i = props.keySet().iterator();
while (i.hasNext()) {
String key = (String)i.next();
for (String key : props.stringPropertyNames()) {
if (key.startsWith("file.") && key.endsWith(".dimensions")) {
String name = key.substring("file.".length(), key.length() - ".dimensions".length());
if (!entryMap.containsKey(name)) {
File f = new File(file, name);
String type = props.getProperty("file." + name + ".type", "image");
FileEntry entry = new FileEntry(this, f, type);
Thumbnail thumbnail = new Thumbnail(f, type);
entry.setCaption(props.getProperty("file." + name + ".caption"));
Date fileDate = sdf.parse(props.getProperty("file." + name + ".captureDate"));
if (fileDate.before(oldest)) {
oldest = fileDate;
}
entry.setDate(fileDate);
thumbnail.setSize(new Dimension(props.getProperty("file." + name + ".dimensions")));
thumbnail.setOrientation(Integer.parseInt(props.getProperty("file." + name + ".orientation")));
thumbnail.setEtag(props.getProperty("file." + name + ".etag"));
entry.setThumbnail(thumbnail);
boolean hidden = Boolean.parseBoolean(props.getProperty("file." + name + ".hidden"));
if (!hidden) {
children.add(entry);
if (name != null && name.equals(coverFileName)) {
setThumbnail(thumbnail);
}
}
String duration = props.getProperty("file." + name + ".length");
if (duration != null) {
thumbnail.setDuration(duration);
File f = new File(file, name);
String type = props.getProperty("file." + name + ".type", "image");
FileEntry entry = new FileEntry(this, f, type);
Thumbnail thumbnail = new Thumbnail(f, type);
entry.setCaption(props.getProperty("file." + name + ".caption"));
Date fileDate = sdf.parse(props.getProperty("file." + name + ".captureDate"));
entry.setDate(fileDate);
thumbnail.setSize(new Dimension(props.getProperty("file." + name + ".dimensions")));
thumbnail.setOrientation(Integer.parseInt(props.getProperty("file." + name + ".orientation")));
thumbnail.setEtag(props.getProperty("file." + name + ".etag"));
entry.setThumbnail(thumbnail);
boolean hidden = Boolean.parseBoolean(props.getProperty("file." + name + ".hidden"));
if (!hidden) {
children.add(entry);
if (name.equals(coverFileName)) {
setThumbnail(thumbnail);
}
}
String duration = props.getProperty("file." + name + ".length");
if (duration != null) {
thumbnail.setDuration(duration);
}
} else if (key.startsWith("dir.") && !key.endsWith(".hidden") && !key.endsWith(".caption")) {
String name = key.substring("dir.".length());
boolean hidden = Boolean.parseBoolean(props.getProperty("dir." + name + ".hidden"));
if (!hidden) {
DirectoryEntry dir = new DirectoryEntry(services, this, new File(file, name));
if (name != null && name.equals(coverFileName)) {
if (name.equals(coverFileName)) {
setThumbnail(dir.getThumbnail());
}
Date fileDate = dir.getEarliest();
if (fileDate != null && fileDate.before(oldest)) {
oldest = fileDate;
}
dir.setDate(fileDate);
dir.setCaption(props.getProperty("dir." + name + ".caption"));
if (!dir.getContents().isEmpty()) {
children.add(dir);
@ -197,7 +182,10 @@ public class DirectoryEntry extends EntryWithChildren<Entry> {
}
}
}
this.earliest = oldest;
this.earliest = children.stream()
.map(Entry::getDate)
.min(Date::compareTo)
.orElse(new Date(file.lastModified()));
this.groupByYear = "year".equalsIgnoreCase(props.getProperty("group"));
if (thumbnail == null && !children.isEmpty()) {
setThumbnail(children.get(0).getThumbnail());

View file

@ -27,7 +27,6 @@ import org.slf4j.LoggerFactory;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.MetadataException;
import com.drew.metadata.exif.ExifIFD0Directory;
import com.drew.metadata.exif.ExifSubIFDDirectory;
import com.drew.metadata.jpeg.JpegDirectory;
@ -60,6 +59,9 @@ public class DirectoryMetadataGenerator {
private void generateFileEntries(File dir, Properties props) throws IOException, InterruptedException {
File[] files = dir.listFiles();
if (files == null) {
return;
}
for (File f : files) {
if (f.length() == 0) {
@ -134,8 +136,6 @@ public class DirectoryMetadataGenerator {
boolean hasOrientation = false;
boolean hasDim = false;
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss");
NumberFormat nf = new DecimalFormat("0");
Metadata metadata;
try {
@ -143,6 +143,9 @@ public class DirectoryMetadataGenerator {
} catch (ImageProcessingException e) {
// not a JPEG file
return null;
} catch (RuntimeException e) {
log.error("Unexpected error processing file " + f.getAbsolutePath(), e);
return null;
}
try {
Directory exifDirectory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
@ -205,7 +208,7 @@ public class DirectoryMetadataGenerator {
}
private Date getExifDate(Directory exifDirectory, int tagName) throws MetadataException {
private Date getExifDate(Directory exifDirectory, int tagName) {
String dateStr = exifDirectory.getString(tagName);
if (" : : : : ".equals(dateStr)) {
@ -216,16 +219,10 @@ public class DirectoryMetadataGenerator {
private Dimension decodeImageForDimensions(File file) throws IOException {
String suffix = null;
String name = file.getName();
if (name.indexOf('.') > 0) {
suffix = name.substring(name.lastIndexOf('.') + 1);
}
Iterator<ImageReader> readers = ImageIO.getImageReadersBySuffix(suffix);
if (!readers.hasNext()) {
ImageReader reader = getImageReader(file);
if (reader == null) {
return null;
}
ImageReader reader = readers.next();
ImageInputStream iis = ImageIO.createImageInputStream(file);
reader.setInput(iis, true);
ImageReadParam param = reader.getDefaultReadParam();
@ -234,6 +231,19 @@ public class DirectoryMetadataGenerator {
return new Dimension(img.getWidth(), img.getHeight());
}
private ImageReader getImageReader(File file) {
String name = file.getName();
if (name.indexOf('.') < 0) {
return null;
}
String suffix = name.substring(name.lastIndexOf('.') + 1);
Iterator<ImageReader> readers = ImageIO.getImageReadersBySuffix(suffix);
if (!readers.hasNext()) {
return null;
}
ImageReader reader = readers.next();
return reader;
}
private Map<String, String> generatePdfProperties(File file) throws IOException {

View file

@ -13,7 +13,7 @@ import java.util.List;
*/
public class EntryWithChildren<T extends Entry> extends Entry {
protected List<T> children = new ArrayList<T>();
protected List<T> children = new ArrayList<>();
protected EntryWithChildren(Entry root) {
super(root);

View file

@ -9,14 +9,7 @@ import java.util.HashMap;
public class SearchResults extends EntryWithChildren<SearchEntry> {
protected HashMap<File, SearchEntry> dupes = new HashMap<File, SearchEntry>();
Comparator<SearchEntry> sort = new Comparator<SearchEntry>() {
@Override
public int compare(SearchEntry e1, SearchEntry e2) {
return Integer.valueOf(e2.score).compareTo(e1.score);
}
};
Comparator<SearchEntry> sort = (e1, e2) -> Integer.valueOf(e2.score).compareTo(e1.score);
protected SearchResults(DirectoryEntry root) {

View file

@ -1,9 +0,0 @@
package org.forkalsrud.album.video;
public interface EncodingProcessListener {
public abstract void chunkAvailable(int chunkNo);
public abstract void codingFinished(int lastCunkNo);
}

View file

@ -474,7 +474,7 @@ public class FlvMetadata {
seconds += 3600 * Double.parseDouble(durationStr.substring(0, pm));
}
setDuration(seconds);
} else {
} else if (durationStr != null) {
setDuration(Double.parseDouble(durationStr));
}
}

View file

@ -6,8 +6,12 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
import org.apache.commons.io.IOUtils;
import org.forkalsrud.album.db.Chunk;
@ -19,12 +23,12 @@ import org.forkalsrud.album.web.PictureScaler;
public class MovieCoder {
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MovieCoder.class);
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MovieCoder.class);
private String ffmpegExecutable;
private String mplayerExecutable;
private PictureScaler pictureScaler;
private HashMap<String, EncodingProcess> currentEncodings = new HashMap<String, EncodingProcess>();
private final PictureScaler pictureScaler;
private final HashMap<String, EncodingProcess> currentEncodings = new HashMap<>();
public MovieCoder(PictureScaler pictureScaler) {
this.pictureScaler = pictureScaler;
@ -52,7 +56,7 @@ public class MovieCoder {
return temp;
}
public void deleteTempDirectory(File dir) {
private void deleteTempDirectory(File dir) {
for (File sub : dir.listFiles()) {
if (sub.isDirectory()) {
deleteTempDirectory(sub);
@ -74,8 +78,7 @@ public class MovieCoder {
p.getOutputStream().close();
log.debug(IOUtils.toString(p.getInputStream()));
p.waitFor();
CachedImage ci = pictureScaler.scalePicture(frame, thumbnail, size);
return ci;
return pictureScaler.scalePicture(frame, thumbnail, size);
} finally {
deleteTempDirectory(tmpDir);
}
@ -84,11 +87,11 @@ public class MovieCoder {
/**
* @param file
* @param file the file to encode
* @param thumbnail
* @param targetSize
* @param key
* @return
* @return a handle to the encoding process
*/
private synchronized EncodingProcess submitEncodingJob(File file,
Thumbnail thumbnail, Dimension targetSize, String key, MovieDatabase movieDb) {
@ -105,20 +108,19 @@ public class MovieCoder {
class EncodingProcess implements Runnable, FlvFilter.FlvReceiver {
private final int chunkSize = 4 * 65536;
private File file;
private Dimension targetSize;
private ArrayList<EncodingProcessListener> listeners = new ArrayList<EncodingProcessListener>();
private final File file;
private final Dimension targetSize;
private Chunk currentChunk = null;
private int chunkPos;
private int remainingCapacity;
private volatile int chunkInProgress = 0;
private final AtomicInteger chunkInProgress;
private volatile int chunkAvailable = 0;
private FlvFilter filter;
private String dbKey;
private long fileTimestamp;
private int orientation;
private final FlvFilter filter;
private final String dbKey;
private final long fileTimestamp;
private final int orientation;
private volatile boolean done = false;
private MovieDatabase movieDb;
private final MovieDatabase movieDb;
public EncodingProcess(File file, Thumbnail thumbnail, Dimension size, MovieDatabase movieDb) {
@ -128,6 +130,7 @@ public class MovieCoder {
this.dbKey = key(file, targetSize);
FlvMetadata extraMeta = new FlvMetadata();
extraMeta.setDuration(thumbnail.getDuration());
this.chunkInProgress = new AtomicInteger(0);
this.filter = new FlvFilter(this, extraMeta);
this.orientation = thumbnail.getOrientation();
this.movieDb = movieDb;
@ -138,7 +141,7 @@ public class MovieCoder {
* 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
* 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:
*
@ -174,25 +177,7 @@ public class MovieCoder {
try {
ArrayList<String> command = new ArrayList<String>();
command.add(ffmpegExecutable);
command.add("-i");
command.add(file.getAbsolutePath());
command.add("-s");
command.add(targetSize.getWidth() + "x" + targetSize.getHeight());
command.add("-crf");
command.add("30");
command.add("-acodec"); command.add("libmp3lame");
command.add("-ar"); command.add("22050");
command.add("-vcodec"); command.add("libx264");
command.add("-g"); command.add("150");
if (vf != null) {
command.add("-vf");
command.add(vf);
}
command.add("-f"); command.add("flv");
command.add("-");
ProcessBuilder pb = new ProcessBuilder(command);
ProcessBuilder pb = createTranscoderProcess(vf);
log.info(pb.command().toString());
pb.redirectErrorStream(false);
@ -229,6 +214,34 @@ public class MovieCoder {
}
}
private ProcessBuilder createTranscoderProcess(String vf) {
ArrayList<String> command = new ArrayList<>();
command.add(ffmpegExecutable);
command.add("-i");
command.add(file.getAbsolutePath());
command.add("-s");
command.add(targetSize.getWidth() + "x" + targetSize.getHeight());
command.add("-crf");
command.add("30");
command.add("-acodec");
command.add("libmp3lame");
command.add("-ar");
command.add("22050");
command.add("-vcodec");
command.add("libx264");
command.add("-g");
command.add("150");
if (vf != null) {
command.add("-vf");
command.add(vf);
}
command.add("-f");
command.add("flv");
command.add("-");
ProcessBuilder pb = new ProcessBuilder(command);
return pb;
}
/**
* @return number of chunks available.
* -Integer.MAX_VALUE if something went very wrong
@ -257,9 +270,9 @@ public class MovieCoder {
int len = data.length;
Chunk chunk0 = new Chunk(fileTimestamp, len, 0);
chunk0.bits = data;
log.info("Writing " + dbKey + " seq 0 (" + chunkInProgress + ") " + data.length);
log.info("Writing {} seq 0 ({}) {}", dbKey, chunkInProgress, data.length);
movieDb.store(dbKey, 0, chunk0);
notifyListeners(chunkInProgress);
notifyListeners(chunkInProgress.get());
}
@Override
@ -289,7 +302,7 @@ public class MovieCoder {
private void startNewChunk() {
this.chunkInProgress++;
this.chunkInProgress.incrementAndGet();
this.currentChunk = new Chunk(fileTimestamp, chunkSize, 0);
this.chunkPos = 0;
this.remainingCapacity = chunkSize;
@ -297,11 +310,11 @@ public class MovieCoder {
private void endChunk() {
log.info("store chunk " + chunkInProgress);
movieDb.store(dbKey, chunkInProgress, currentChunk);
log.info("Writing " + dbKey + " seq " + chunkInProgress + " (" + chunkInProgress + ") " + currentChunk.bits.length);
log.info("store chunk {}", chunkInProgress);
movieDb.store(dbKey, chunkInProgress.get(), currentChunk);
log.info("Writing {} seq {} ({}) {}", dbKey, chunkInProgress, chunkInProgress, currentChunk.bits.length);
currentChunk = null;
notifyListeners(chunkInProgress);
notifyListeners(chunkInProgress.get());
}
private void endLastChunk() {
@ -312,24 +325,16 @@ public class MovieCoder {
// reallocate
Chunk last = new Chunk(fileTimestamp, chunkPos, 0);
System.arraycopy(currentChunk.bits, 0, last.bits, 0, chunkPos);
movieDb.store(dbKey, chunkInProgress, last);
log.info("Writing " + dbKey + " seq " + chunkInProgress + " (" + chunkInProgress + ") " + last.bits.length);
movieDb.store(dbKey, chunkInProgress.get(), last);
log.info("Writing {} seq {} ({}) {}", dbKey, chunkInProgress, chunkInProgress, last.bits.length);
currentChunk = null;
}
public synchronized void addListener(EncodingProcessListener videoStreamer) {
listeners.add(videoStreamer);
}
public synchronized void removeListener(VideoStreamer videoStreamer) {
listeners.remove(videoStreamer);
}
}
class ErrorStreamPumper implements Runnable {
static class ErrorStreamPumper implements Runnable {
private InputStream is;
private String name;
private final InputStream is;
private final String name;
public ErrorStreamPumper(String processName, InputStream is) {
this.name = processName;
@ -338,12 +343,12 @@ public class MovieCoder {
@Override
public void run() {
org.slf4j.Logger diag = org.slf4j.LoggerFactory.getLogger(this.name);
org.slf4j.Logger diagnostic = org.slf4j.LoggerFactory.getLogger(this.name);
try {
LineNumberReader lnr = new LineNumberReader(new InputStreamReader(is));
String line;
while ((line = lnr.readLine()) != null) {
diag.info(line);
diagnostic.info(line);
}
} catch (Exception e) {
log.error("stderr?", e);
@ -382,7 +387,7 @@ public class MovieCoder {
// If the file has been modified since our last encoding job finished
if (chunk != null && ep == null) {
if (chunk.timestamp != file.lastModified()) {
log.info(" " + key + " has changed so cache entry wil be refreshed");
log.info(" {} has changed so cache entry wil be refreshed", key);
movieDb.delete(key);
chunk = null;
}
@ -398,11 +403,11 @@ public class MovieCoder {
class VideoStreamer {
private int chunkNo = 0;
private EncodingProcess ep;
private final EncodingProcess ep;
private Chunk chunk;
private String key;
private final String key;
private boolean done = false;
private MovieDatabase movieDb;
private final MovieDatabase movieDb;
private VideoStreamer(String key, EncodingProcess ep, Chunk chunk0, MovieDatabase movieDb) {
@ -424,11 +429,11 @@ public class MovieCoder {
while (!done) {
if (chunk == null) {
log.info("Looking for " + key + " - " + chunkNo);
log.info("Looking for {} - {}", key, chunkNo);
chunk = movieDb.load(key, chunkNo);
}
if (chunk != null) {
log.info("Sending " + chunkNo + ", " + chunk.bits.length + " bytes");
log.info("Sending {}, {} bytes", chunkNo, chunk.bits.length);
out.write(chunk.bits);
chunk = null;
chunkNo++;

View file

@ -20,18 +20,13 @@ import javax.servlet.http.HttpServletResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.PropertyConfigurator;
import org.forkalsrud.album.db.DirectoryDatabase;
import org.forkalsrud.album.db.DirectoryProps;
import org.forkalsrud.album.db.MovieDatabase;
import org.forkalsrud.album.db.ThumbnailDatabase;
import org.forkalsrud.album.exif.DirectoryEntry;
import org.forkalsrud.album.exif.DirectoryEntryFactory;
import org.forkalsrud.album.exif.Entry;
import org.forkalsrud.album.exif.FileEntry;
import org.forkalsrud.album.exif.SearchEngine;
import org.forkalsrud.album.exif.SearchResults;
import org.forkalsrud.album.exif.Thumbnail;
import org.forkalsrud.album.exif.*;
import org.forkalsrud.album.video.MovieCoder;
import org.springframework.web.util.HtmlUtils;
@ -42,7 +37,7 @@ import com.sleepycat.je.EnvironmentConfig;
public class AlbumServlet
extends HttpServlet
{
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AlbumServlet.class);
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AlbumServlet.class);
static void addDummyLoggerFor(String... names) {
for (String name : names)
@ -78,7 +73,6 @@ public class AlbumServlet
}
});
}
hh = ll.getHandlers();
} catch (Exception e) {
e.printStackTrace();
}
@ -101,9 +95,9 @@ public class AlbumServlet
* maps files to URIs relative to servlet, e.g. /home/joe/photos/holiday/arrival.jpg -> /photos/holiday/arrival.jpg
* assuming base is /home/joe/photos
*/
public class Mapper {
public static class Mapper {
private File base;
private final File base;
public Mapper(File base) {
this.base = base;
@ -114,7 +108,7 @@ public class AlbumServlet
return appendFile(buf, file).toString();
}
StringBuilder appendFile(StringBuilder buf, File file) {
private StringBuilder appendFile(StringBuilder buf, File file) {
if (file == null) {
return buf;
}
@ -124,16 +118,6 @@ public class AlbumServlet
return appendFile(buf, file.getParentFile()).append('/').append(file.getName());
}
}
Calendar cal = Calendar.getInstance();
public String year(Date d) {
if (d == null) {
return "";
}
cal.setTime(d);
return String.valueOf(cal.get(Calendar.YEAR));
}
}
@ -144,7 +128,7 @@ public class AlbumServlet
String basePrefix;
DirectoryEntryFactory dirEntryFactory;
private Environment environment;
private final Environment environment;
ThumbnailDatabase thumbDb;
DirectoryDatabase dirDb;
MovieDatabase movieDb;
@ -225,8 +209,8 @@ public class AlbumServlet
res.setContentType("text/html");
req.setAttribute("search", query);
req.setAttribute("entry", results);
req.setAttribute("thmb", new Integer(250));
req.setAttribute("full", new Integer(800));
req.setAttribute("thmb", 250);
req.setAttribute("full", 800);
RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/velocity/photo.vm");
rd.forward(req, res);
}
@ -235,7 +219,7 @@ public class AlbumServlet
void handlePhoto(HttpServletRequest req, HttpServletResponse res, FileEntry entry) throws Exception {
res.setContentType("text/html");
req.setAttribute("entry", entry);
req.setAttribute("thmb", new Integer(800));
req.setAttribute("thmb", 800);
RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/velocity/photo.vm");
rd.forward(req, res);
}
@ -243,20 +227,20 @@ public class AlbumServlet
void handleAlbum(HttpServletRequest req, HttpServletResponse res, DirectoryEntry entry) throws Exception {
res.setContentType("text/html");
req.setAttribute("entry", entry);
req.setAttribute("thmb", new Integer(250));
req.setAttribute("full", new Integer(800));
req.setAttribute("thmb", 250);
req.setAttribute("full", 800);
req.setAttribute("D", "$");
RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/ng.html");
rd.forward(req, res);
}
void handleMovieFrame(HttpServletRequest req, HttpServletResponse res, FileEntry entry) throws Exception {
void handleMovieFrame(HttpServletRequest req, HttpServletResponse res, FileEntry entry) {
File file = entry.getPath();
if (notModified(req, file)) {
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
res.setDateHeader("Expires", System.currentTimeMillis() + (30 * 24 * 3600 * 1000L)); // 30 days
log.info(file.getName() + " not modified (based on date)");
log.info("{} not modified (based on date)", file.getName());
return;
}
int secondNo = 3;
@ -271,7 +255,7 @@ public class AlbumServlet
if (cimg.lastModified >= fileTs) {
// log.info("cache hit on " + key);
} else {
log.info(" " + key + " has changed so cache entry wil be refreshed");
log.info(" {} has changed so cache entry wil be refreshed", key);
cimg = null;
}
}
@ -279,7 +263,7 @@ public class AlbumServlet
try {
cimg = movieCoder.extractFrame(file, secondNo, entry.getThumbnail(), size);
thumbDb.store(key, cimg);
log.info(" " + key + " added to the cache with size " + cimg.bits.length + " -- now " + thumbDb.size() + " entries");
log.info(" {} added to the cache with size {} -- now {} entries", key, cimg.bits.length, thumbDb.size());
} catch (Exception e) {
//e.fillInStackTrace();
throw new RuntimeException("sadness", e);
@ -303,11 +287,29 @@ public class AlbumServlet
if (notModified(req, file)) {
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
res.setDateHeader("Expires", System.currentTimeMillis() + (30 * 24 * 3600 * 1000L)); // 30 days
log.info(file.getName() + " not modified (based on date)");
log.info("{} not modified (based on date)", file.getName());
return;
}
String size = req.getParameter("size");
if (size == null) {
try (FileInputStream in = new FileInputStream(file);
OutputStream out = res.getOutputStream()) {
res.setStatus(HttpServletResponse.SC_OK);
res.setDateHeader("Last-Modified", entry.getPath().lastModified());
res.setDateHeader("Expires", System.currentTimeMillis() + (30 * 24 * 3600 * 1000L)); // 30 days
res.setContentType("video/mp4");
IOUtils.copyLarge(in, out);
} catch (IOException e) {
log("sadness", e);
}
return;
}
try {
String size = req.getParameter("size");
res.setStatus(HttpServletResponse.SC_OK);
res.setDateHeader("Last-Modified", entry.getPath().lastModified());
res.setDateHeader("Expires", System.currentTimeMillis() + (30 * 24 * 3600 * 1000L)); // 30 days
@ -323,6 +325,18 @@ public class AlbumServlet
void handleJson(HttpServletRequest req, HttpServletResponse res, DirectoryEntry entry) {
try {
EntryWithChildren<?> contents;
String query = req.getParameter("q");
if (query != null && !"".equals(query.trim())) {
SearchEngine search = new SearchEngine(entry);
contents = search.search(query);
} else {
contents = entry;
}
Mapper mapper = new Mapper(base);
res.setContentType("application/json");
res.setCharacterEncoding("UTF-8");
@ -331,29 +345,23 @@ public class AlbumServlet
ObjectNode root = json.createObjectNode();
root.put("name", entry.getName());
if (entry.parent() != null && "dir".equals(entry.parent().getType())) {
root.put("parent", mapper.map(entry.parent().getPath()));
}
if (entry.prev() != null && "dir".equals(entry.prev().getType())) {
root.put("prev", mapper.map(entry.prev().getPath()));
}
if (entry.next() != null && "dir".equals(entry.next().getType())) {
root.put("next", mapper.map(entry.next().getPath()));
}
addDir(root, entry.parent(), "parent", mapper);
addDir(root, entry.prev(), "prev", mapper);
addDir(root, entry.next(), "next", mapper);
if (entry.groupByYear()) {
root.put("groupPrefix", 4);
}
ArrayNode contents = root.putArray("contents");
for (Entry e : entry.getContents()) {
ArrayNode list = root.putArray("contents");
for (Entry e : contents.getContents()) {
try {
ObjectNode item = contents.addObject();
ObjectNode item = list.addObject();
item.put("name", e.getName());
item.put("type", e.getType());
if ("dir".equals(e.getType())) {
DirectoryEntry de = (DirectoryEntry)e;
if (de.getEarliest() != null) {
if (e.getEarliest() != null) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
item.put("earliest", sdf.format(de.getEarliest()));
item.put("earliest", sdf.format(e.getEarliest()));
}
}
Thumbnail thumb = e.getThumbnail();
@ -377,6 +385,14 @@ public class AlbumServlet
}
void addDir(ObjectNode root, Entry candidate, String label, Mapper mapper) {
if (candidate != null && "dir".equals(candidate.getType())) {
root.put(label, mapper.map(candidate.getPath()));
}
}
void handleCache(HttpServletRequest req, HttpServletResponse res, DirectoryEntry entry) {
if (entry == null) {
res.setStatus(HttpServletResponse.SC_NOT_FOUND);
@ -419,7 +435,7 @@ public class AlbumServlet
}
res.setContentType("text/html");
req.setAttribute("entry", entry);
req.setAttribute("thmb", new Integer(640));
req.setAttribute("thmb", 640);
RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/velocity/edit.vm");
rd.forward(req, res);
}
@ -433,14 +449,14 @@ public class AlbumServlet
if (notModified(req, file)) {
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
res.setDateHeader("Expires", System.currentTimeMillis() + (30 * 24 * 3600 * 1000L)); // 30 days
log.info(file.getName() + " not modified (based on date)");
log.info("{} not modified (based on date)", file.getName());
return;
}
String fileEtag = thumbnail.getEtag() + "-" + size;
if (etagMatches(req, fileEtag)) {
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
res.setDateHeader("Expires", System.currentTimeMillis() + (30 * 24 * 3600 * 1000L)); // 30 days
log.info(file.getName() + " not modified (based on etag)");
log.info("{} not modified (based on etag)", file.getName());
return;
}
@ -451,14 +467,15 @@ public class AlbumServlet
if (cimg.lastModified == file.lastModified()) {
// log.info("cache hit on " + key);
} else {
log.info(" " + key + " has changed so cache entry wil be refreshed");
log.info(" {} has changed so cache entry wil be refreshed", key);
cimg = null;
}
}
if (cimg == null) {
cimg = pictureScaler.scalePicture(file, thumbnail, size);
thumbDb.store(key, cimg);
log.info(" " + key + " added to the cache with size " + cimg.bits.length + " -- now " + thumbDb.size() + " entries");
int byteSize = cimg.bits != null ? cimg.bits.length : 0;
log.info(" {} added to the cache with size {} -- now {} entries", key, byteSize, thumbDb.size());
}
res.setStatus(HttpServletResponse.SC_OK);
res.setDateHeader("Last-Modified", file.lastModified());
@ -472,7 +489,7 @@ public class AlbumServlet
Map<String, Root> roots = new HashMap<String, Root>();
Map<String, Root> roots = new HashMap<>();
PictureScaler pictureScaler;
@ -559,7 +576,7 @@ public class AlbumServlet
@Override
public void destroy() {
log.info("Shutting down Album");
roots.values().forEach(r -> r.close());
roots.values().forEach(Root::close);
}
@ -598,19 +615,25 @@ public class AlbumServlet
// help the user get to the top level page
if (pathInfo == null || "/".equals(pathInfo)) {
Root root = roots.values().stream().findFirst().orElse(null);
String u = req.getContextPath() + "/album/" + root.getName() + ".album";
res.sendRedirect(u);
return;
String rootPath = roots.values().stream()
.findFirst()
.map(r -> req.getContextPath() + "/album/" + r.getName() + ".album")
.orElse(null);
if (rootPath != null) {
res.sendRedirect(rootPath);
} else {
res.setStatus(404);
}
return;
}
if ("/_roots.json".equals(pathInfo)) {
res.setContentType("application/json");
res.setContentType("application/json");
res.setCharacterEncoding("UTF-8");
ObjectMapper json = new ObjectMapper();
ArrayNode arr = json.createArrayNode();
roots.values().stream().map(r -> r.getName()).forEach(arr::add);
roots.values().stream().map(Root::getName).forEach(arr::add);
json.writeValue(res.getOutputStream(), arr);
return;
}
@ -690,8 +713,9 @@ public class AlbumServlet
}
String size = req.getParameter("size");
if (size != null) {
if ("max".equals(size)) {
getServletContext().getNamedDispatcher("file").forward(req, res);
} else if (size != null) {
FileEntry e = (FileEntry)root.resolve(file);
root.procesScaledImageRequest(req, res, file, e.getThumbnail(), size);
return;

View file

@ -7,7 +7,6 @@ import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.BlockingQueue;
@ -26,10 +25,7 @@ import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSDocument;
import org.apache.pdfbox.io.RandomAccessRead;
import org.apache.pdfbox.io.RandomAccessReadBufferedFile;
import org.apache.pdfbox.io.RandomAccessReadMemoryMappedFile;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
@ -47,9 +43,10 @@ public class PictureScaler {
public PictureScaler() {
queue = new PriorityBlockingQueue<PictureRequest>(20, createPriorityComparator());
queue = new PriorityBlockingQueue<>(20, (o1, o2) -> Long.signum(o1.priority - o2.priority));
executor = Executors.newFixedThreadPool(4);
outstandingRequests = new HashMap<String, PictureRequest>();
outstandingRequests = new HashMap<>();
}
@ -119,19 +116,8 @@ public class PictureScaler {
}
}
Comparator<PictureRequest> createPriorityComparator() {
return new Comparator<PictureRequest>() {
@Override
public int compare(PictureRequest o1, PictureRequest o2) {
return Long.signum(o1.priority - o2.priority);
}
};
}
public CachedImage scalePicture(File file, Thumbnail thumbnail, String size) {
public CachedImage scalePicture(File file, Thumbnail thumbnail, String size) {
String key = file.getPath() + ":" + size;
return scalePicture(key, file, thumbnail, size);
}
@ -181,6 +167,13 @@ public class PictureScaler {
}
BufferedImage img = ImageIO.read(file);
if (img == null) {
CachedImage cimg = new CachedImage();
cimg.lastModified = file.lastModified();
cimg.mimeType = "image/jpeg";
cimg.bits = new byte[0];
return cimg;
}
// The orientation is about flipping and rotating. Here is what an 'F' looks like
// on pictures with each orientation.

View file

@ -6,13 +6,9 @@
<title>album</title>
<script type="text/javascript" src="/photo/assets/jquery/jquery-3.7.1.min.js"></script>
<!--
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.min.js"></script>
-->
<script type="text/javascript" src="/photo/assets/fancybox/jquery.fancybox-1.3.1.js"></script>
<script type="text/javascript" src="/photo/assets/fancybox/jquery.easing-1.3.pack.js"></script>
<script type="text/javascript" src="/photo/assets/fancybox/jquery.mousewheel-3.0.2.pack.js"></script>
<script type="text/javascript" src="/photo/assets/flowplayer/flowplayer-3.0.3.min.js"></script>
<link rel="stylesheet" href="/photo/assets/fancybox/jquery.fancybox-1.3.1.css" type="text/css" media="screen" />
@ -30,7 +26,6 @@
text-align: left;
}
form {
float: right;
display: inline;
}
h2 {
@ -62,7 +57,7 @@
max-width: 275px;
overflow: hidden;
}
img.imagepic {
img.imagepic, img.moviepic {
box-shadow: 5px 5px 5px #777;
-webkit-box-shadow: 5px 5px 5px #777;
-moz-box-shadow: 5px 5px 5px #777;
@ -81,17 +76,26 @@
vertical-align: middle;
}
#fancybox-left, #fancybox-right {
bottom: 30px;
bottom: 70px;
}
</style>
<script type="text/javascript" src="/photo/assets/render.js"></script>
</head>
<body>
<form id="search" action="/photo/album/search" method="get"><input name="q" value=""></form>
<h1 id="titleBar" style="height: 32;"><!--
--><img class="nav" width="26" height="32" src="/photo/assets/left-inactive.png"><!--
--><img class="nav" width="26" height="32" src="/photo/assets/up-inactive.png"><!--
--><img class="nav" width="26" height="32" src="/photo/assets/right-inactive.png">&nbsp;</h1>
<form id="search" action="/photo/album/search" method="get"><!--
--><h1 id="titleBar" style="height: 32;"><!--
--><img id="arrowLeft" class="nav" width="26" height="32" src="/photo/assets/left-inactive.png"><!--
--><img id="arrowUp" class="nav" width="26" height="32" src="/photo/assets/up-inactive.png"><!--
--><img id="arrowRight" class="nav" width="26" height="32" src="/photo/assets/right-inactive.png"><!--
-->&nbsp;<!--
--><span id="entryName"></span><!--
-->&nbsp;<!--
--><input id="searchBox" type="search" name="q" value=""><!--
--><input id="reset" type="reset" value="X" alt="Clear the search form"><!--
--><input id="submit" type="submit" value="Search"><!--
--></h1><!--
--></form>
<hr/>
<div id="container"></div>
</body>
</html>

View file

@ -20,7 +20,10 @@
selectedIndex = 0, selectedOpts = {}, selectedArray = [], currentIndex = 0, currentOpts = {}, currentArray = [],
ajaxLoader = null, imgPreloader = new Image(), imgRegExp = /\.(jpg|gif|png|bmp|jpeg)(.*)?$/i, swfRegExp = /[^\.]\.(swf)\s*$/i,
ajaxLoader = null, imgPreloader = new Image(),
imgRegExp = /\.(jpg|gif|png|bmp|jpeg)(.*)?$/i,
movieRegExp = /\.(mov)(.*)?$/i,
swfRegExp = /[^\.]\.(swf)\s*$/i,
loadingTimer, loadingFrame = 1,
@ -240,7 +243,7 @@
},
_finish = function () {
inner.css('overflow', (currentOpts.scrolling == 'auto' ? (currentOpts.type == 'image' || currentOpts.type == 'iframe' || currentOpts.type == 'swf' ? 'hidden' : 'auto') : (currentOpts.scrolling == 'yes' ? 'auto' : 'visible')));
inner.css('overflow', (currentOpts.scrolling == 'auto' ? (currentOpts.type == 'image' || currentOpts.type == 'iframe' || currentOpts.type == 'swf' || currentOpts.type == 'movie' ? 'hidden' : 'auto') : (currentOpts.scrolling == 'yes' ? 'auto' : 'visible')));
if (!$.support.opacity) {
inner.get(0).style.removeProperty('filter');
@ -550,6 +553,9 @@
if (href.match(imgRegExp)) {
type = 'image';
} else if (href.match(movieRegExp)) {
type = 'movie';
} else if (href.match(swfRegExp)) {
type = 'swf';
@ -571,7 +577,7 @@
selectedOpts.href = href;
selectedOpts.title = title;
if (selectedOpts.autoDimensions && selectedOpts.type !== 'iframe' && selectedOpts.type !== 'swf') {
if (selectedOpts.autoDimensions && selectedOpts.type !== 'iframe' && selectedOpts.type !== 'swf' && selectedOpts.type !== 'movie') {
selectedOpts.width = 'auto';
selectedOpts.height = 'auto';
}
@ -636,6 +642,13 @@
break;
case 'movie':
str = '<video controls width="' + selectedOpts.width + '" height="' + selectedOpts.height + '" '
+ 'src="' + href + '" poster="' + selectedOpts.poster + '"></video>'
tmp.html(str);
fancybox_process_inline();
break;
case 'swf':
str = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="' + selectedOpts.width + '" height="' + selectedOpts.height + '"><param name="movie" value="' + href + '"></param>';
emb = '';

View file

@ -1,7 +1,7 @@
$(function() {
function formatTitle(title, currentArray, currentIndex, currentOpts) {
function formatCaption(title, currentArray, currentIndex, currentOpts) {
var captionElement = document.getElementById(title);
return captionElement.innerHTML;
}
@ -15,8 +15,19 @@ $(function() {
}
function encUri(str) {
return encodeURI(str)
.replace(/%5B/g, "[")
.replace(/%5D/g, "]")
.replace(/[!'()*?&#]/g, function (c) {
return "%" + c.charCodeAt(0).toString(16).toUpperCase();
});
}
function renderGrid(data, textStatus) {
var container = $("#container");
container.empty();
$("#name").text(data.name);
document.title = data.name;
@ -25,89 +36,96 @@ $(function() {
var movieSize = 640;
var prevName = null, thisName;
function updateButton(selector, direction, destination) {
var imgName = destination ? direction : direction + "-inactive",
imgUrl = "/photo/assets/" + imgName + ".png",
imgElement = $(selector),
isWrapped = imgElement.parent().get()[0].tagName == "A",
anchor = isWrapped ? imgElement.parent() : $("<a></a>");
if (destination) {
anchor.attr("href", encUri("/photo/album" + destination + ".album") + location.search);
if (!isWrapped) {
imgElement.wrap(anchor);
}
} else {
if (isWrapped) {
imgElement.unwrap();
}
}
imgElement.attr("src", imgUrl);
}
$("#entryName").text(data.name);
$("#arrowLeft").attr(data.name);
updateButton("#arrowLeft", "left", data.prev);
updateButton("#arrowUp", "up", data.parent);
updateButton("#arrowRight", "right", data.next);
if (location.search) {
let params = new URL(location.href).searchParams;
if (params.has("q")) {
$("#searchBox").val(params.get("q"));
}
}
$.each(data.contents, function(idx, entry) {
if (data.groupPrefix && entry.type == "dir") {
thisName = entry.earliest;
if (prevName != null && prevName != thisName) {
$("<hr/>").appendTo("body");
$("<hr/>").appendTo(container);
prevName = null;
}
if (prevName == null) {
$("<h2></h2>").text(thisName).appendTo("body");
$("<h2></h2>").text(thisName).appendTo(container);
prevName = thisName;
}
}
var dim = scale(entry.width, entry.height, thmb);
var href, poster;
if (entry.thumbtype == "movie") {
href = "/photo/album" + encUri(entry.path) + ".movie";
poster = "/photo/album" + encUri(entry.path) + ".frame";
} else {
href = "/photo/album" + encUri(entry.path) + "?size=" + picSize;
poster = "/photo/album" + encUri(entry.path);
}
var gridDiv = $("<div class=\"grid\">\n"
+ " <span class=\"name\">" + entry.name + "</span><br/>\n"
+ " <div class=\"imgborder\"><a class=\"ss\" id=\"ent" + idx + "\" href=\"/photo/album" + encodeURIComponent(entry.path) + "?size=" + picSize + "\" title=\"" + entry.name + "\">"
+ "<img class=\"" + entry.type + "pic\" src=\"/photo/album" + (entry.thumbtype == "movie" ? encodeURIComponent(entry.path) + ".frame" : encodeURIComponent(entry.path)) + "?size=" + thmb + "\" border=\"0\" width=\"" + dim.w + "\" height=\"" + dim.h + "\"/></a></div>\n"
+ " <div class=\"imgborder\"><a class=\"ss\" id=\"ent" + idx + "\" href=\"" + href + "\" title=\"" + entry.name + "\">"
+ "<img class=\"" + entry.type + "pic\" src=\"" + poster + "?size=" + thmb + "\" border=\"0\" width=\"" + dim.w + "\" height=\"" + dim.h + "\"/></a></div>\n"
+ "</div>\n");
var captionP = $("<p id=\"" + entry.name + "\" class=\"caption\"></p>\n");
var captionP = $("<p class=\"caption\"></p>\n");
captionP.attr("id", entry.name);
if (entry.caption) {
captionP.text(entry.caption);
}
captionP.appendTo(gridDiv);
gridDiv.appendTo('body');
gridDiv.appendTo(container);
switch (entry.type) {
case "movie":
var size = scale(entry.width, entry.height, movieSize);
var href = "/photo/album" + escape(entry.path) + ".movie?size=" + movieSize;
$("#ent" + idx).attr("rel", "album").attr("href", href).fancybox({
'titlePosition' : 'inside',
'transitionIn' : 'elastic',
'transitionOut' : 'elastic',
'easingIn' : 'easeInQuad',
'easingOut' : 'easeOutQuad',
'titleFormat' : formatTitle,
'padding' : 0,
'href' : "/photo/assets/flowplayer/flowplayer-3.0.3.swf",
'width' : size.w,
'height' : size.h,
'type' : 'swf',
'swf' : {
'allowfullscreen' : 'true',
'wmode' : 'transparent',
'flashvars':
"config={\
'clip': {\
'url': '" + href + "'\
},\
'plugins': {\
'controls': {\
'url': '/photo/assets/flowplayer/flowplayer.controls-3.0.3.swf',\
'backgroundColor': 'transparent',\
'progressColor': 'transparent',\
'bufferColor': 'transparent',\
'play':true,\
'fullscreen':true,\
'autoHide': 'always'\
}\
}\
}"
}
});
break;
case "image":
var largeDim = scale(entry.width, entry.height, picSize);
$("#ent" + idx).attr("rel", "album").fancybox({
var options = {
'titlePosition' : 'inside',
'transitionIn' : 'elastic',
'transitionOut' : 'elastic',
'easingIn' : 'easeInQuad',
'easingOut' : 'easeOutQuad',
'titleFormat' : formatTitle,
'titleFormat' : formatCaption,
'width' : largeDim.w,
'height' : largeDim.h,
'type' : "image",
'href' : $("#ent" + idx).href
});
'type' : entry.type,
'href' : href
};
if (entry.thumbtype == "movie") {
options['poster'] = poster + "?size=" + largeDim.w + "w";
}
$("#ent" + idx).attr("rel", "album").fancybox(options);
break;
case "dir":
$("#ent" + idx).attr("href", location.pathname.replace(/\.album?($|\?)/, "/" + entry.name + ".album"));
@ -115,43 +133,49 @@ $(function() {
}
function buildButton(direction, destination, transform) {
var imgName = destination ? direction : direction + "-inactive",
imgElement = $("<img class=\"nav\" width=\"26\" height=\"32\" src=\"/photo/assets/" + imgName + ".png\"/>");
if (destination) {
return $("<a></a>").attr("href", "/photo/album" + destination + ".album").append(imgElement);
} else {
return imgElement;
}
}
$("#titleBar").html("").append(buildButton("left", data.prev)).append(buildButton("up", data.parent)).append(buildButton("right", data.next)).append(" " + data.name);
var selectedImg = window.location.search;
var selectedPos = undefined;
if (/^\?focus=/.test(selectedImg)) {
selectedImg = selectedImg.substring('?focus='.length);
$("a.ss").each(function(index) {
if (selectedImg == $(this).attr("title")) {
selectedPos = index;
}
});
}
if (selectedPos !== undefined) {
$("#ent" + selectedPos).trigger('click');
}
});
var selectedImg = window.location.search;
var selectedPos = undefined;
if (/^\?focus=/.test(selectedImg)) {
selectedImg = selectedImg.substring('?focus='.length);
$("a.ss").each(function(index) {
if (selectedImg == $(this).attr("title")) {
selectedPos = index;
}
});
}
if (selectedPos !== undefined) {
$("#ent" + selectedPos).trigger('click');
}
}
$("#search").each(function (id, el) {
el.action = location.pathname.replace(/\.album(\?|$)/, '.search');
$("#search").on("submit", function () {
var searchStr = "?q=" + encUri($("#searchBox").val());
history.pushState({ }, null, searchStr);
$.getJSON(location.pathname.replace(/\.album(\?|$)/, '.json') + searchStr, renderGrid);
return false;
});
$.getJSON(location.pathname.replace(/\.album(\?|$)/, '.json'), renderGrid);
function resetSearch() {
$("#searchBox").val("");
history.pushState({ }, null, location.pathname);
$.getJSON(location.pathname.replace(/\.album(\?|$)/, '.json'), renderGrid);
return false;
}
$("#search").on("reset", resetSearch);
$("#searchBox").keyup(function (e) {
if (e.key === "Escape") {
resetSearch();
}
});
$.getJSON(location.pathname.replace(/\.album(\?|$)/, '.json') + location.search, renderGrid);
});