Compare commits
No commits in common. "9dd452fe3645e33cb1f24a196db765c68b388397" and "9851784fdea3bd146be1a0a5730a69660ff72b87" have entirely different histories.
9dd452fe36
...
9851784fde
17 changed files with 1387 additions and 1461 deletions
|
|
@ -16,8 +16,8 @@
|
||||||
|
|
||||||
Create a file: `~/forkalsrud.org/photo.properties` and for each named "root"
|
Create a file: `~/forkalsrud.org/photo.properties` and for each named "root"
|
||||||
(`photo` in the example below), point the `base` property and `dbdir` properties
|
(`photo` in the example below), point the `base` property and `dbdir` properties
|
||||||
to the directory holding the photos, and the directory to be populated with
|
to the direcory holding the photos, and the diractory to be populated with
|
||||||
BerkeleyDB files to your photo folder:
|
BerkelyDB files to your photo folder:
|
||||||
|
|
||||||
root.photo.base=/home/photo
|
root.photo.base=/home/photo
|
||||||
root.photo.dbdir=/home/resin/web/db
|
root.photo.dbdir=/home/resin/web/db
|
||||||
|
|
|
||||||
18
pom.xml
18
pom.xml
|
|
@ -119,7 +119,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.drewnoakes</groupId>
|
<groupId>com.drewnoakes</groupId>
|
||||||
<artifactId>metadata-extractor</artifactId>
|
<artifactId>metadata-extractor</artifactId>
|
||||||
<version>2.19.0</version>
|
<version>2.18.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.servlet</groupId>
|
<groupId>javax.servlet</groupId>
|
||||||
|
|
@ -247,22 +247,6 @@
|
||||||
<artifactId>pdfbox-io</artifactId>
|
<artifactId>pdfbox-io</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>3.0.0</version>
|
||||||
</dependency>
|
</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>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
|
|
||||||
|
|
@ -93,9 +93,7 @@ public class ThumbnailDatabase extends TupleBinding<CachedImage> {
|
||||||
img.mimeType = in.readString();
|
img.mimeType = in.readString();
|
||||||
int lobLen = in.readInt();
|
int lobLen = in.readInt();
|
||||||
img.bits = new byte[lobLen];
|
img.bits = new byte[lobLen];
|
||||||
if (lobLen > 0) {
|
in.read(img.bits, 0, lobLen);
|
||||||
in.read(img.bits, 0, lobLen);
|
|
||||||
}
|
|
||||||
return img;
|
return img;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,12 +104,8 @@ public class ThumbnailDatabase extends TupleBinding<CachedImage> {
|
||||||
out.writeInt(1); // version 1
|
out.writeInt(1); // version 1
|
||||||
out.writeLong(img.lastModified);
|
out.writeLong(img.lastModified);
|
||||||
out.writeString(img.mimeType);
|
out.writeString(img.mimeType);
|
||||||
if (img.bits != null) {
|
out.writeInt(img.bits.length);
|
||||||
out.writeInt(img.bits.length);
|
out.write(img.bits);
|
||||||
out.write(img.bits);
|
|
||||||
} else {
|
|
||||||
out.writeInt(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -367,7 +367,7 @@ public class Editor
|
||||||
|
|
||||||
int findRegion(int ex, int ey) {
|
int findRegion(int ex, int ey) {
|
||||||
Rectangle r = roi.getScreenRectangle();
|
Rectangle r = roi.getScreenRectangle();
|
||||||
int c;
|
int c = 8;
|
||||||
|
|
||||||
if (within(ex, r.x + r.width)) {
|
if (within(ex, r.x + r.width)) {
|
||||||
if (within(ey, r.y + r.height)) {
|
if (within(ey, r.y + r.height)) {
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,7 @@ public class Dimension {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dimension(String s) {
|
public Dimension(String s) {
|
||||||
String[] coords;
|
String[] coords = s.split("x");
|
||||||
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.w = Integer.parseInt(coords[0]);
|
||||||
this.h = Integer.parseInt(coords[1]);
|
this.h = Integer.parseInt(coords[1]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,11 @@ import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.forkalsrud.album.db.DirectoryDatabase;
|
import org.forkalsrud.album.db.DirectoryDatabase;
|
||||||
|
|
@ -24,14 +27,14 @@ public class DirectoryEntry extends EntryWithChildren<Entry> {
|
||||||
|
|
||||||
public interface ServiceApi {
|
public interface ServiceApi {
|
||||||
|
|
||||||
DirectoryDatabase getDirectoryDatabase();
|
public DirectoryDatabase getDirectoryDatabase();
|
||||||
DirectoryMetadataGenerator getMetadataGenerator();
|
public DirectoryMetadataGenerator getMetadataGenerator();
|
||||||
}
|
}
|
||||||
|
|
||||||
ServiceApi services;
|
ServiceApi services;
|
||||||
DirectoryProps cache;
|
DirectoryProps cache;
|
||||||
boolean childrenLoaded = false;
|
boolean childrenLoaded = false;
|
||||||
Comparator<Entry> ordering = null;
|
Comparator<Entry> sort = null;
|
||||||
Date earliest = null;
|
Date earliest = null;
|
||||||
boolean groupByYear;
|
boolean groupByYear;
|
||||||
|
|
||||||
|
|
@ -126,7 +129,7 @@ public class DirectoryEntry extends EntryWithChildren<Entry> {
|
||||||
|
|
||||||
private void sort() {
|
private void sort() {
|
||||||
|
|
||||||
children.sort(ordering);
|
Collections.sort(children, sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -136,45 +139,57 @@ public class DirectoryEntry extends EntryWithChildren<Entry> {
|
||||||
String coverFileName = props.getProperty("cover");
|
String coverFileName = props.getProperty("cover");
|
||||||
String caption = props.getProperty("caption");
|
String caption = props.getProperty("caption");
|
||||||
setCaption(caption);
|
setCaption(caption);
|
||||||
ordering = ComparatorFactory.getSort(props.getProperty("sort"));
|
sort = ComparatorFactory.getSort(props.getProperty("sort"));
|
||||||
|
|
||||||
|
HashMap<String, Entry> entryMap = new HashMap<String, Entry>();
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss");
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss");
|
||||||
|
|
||||||
for (String key : props.stringPropertyNames()) {
|
Date oldest = new Date(file.lastModified());
|
||||||
|
|
||||||
|
Iterator<Object> i = props.keySet().iterator();
|
||||||
|
while (i.hasNext()) {
|
||||||
|
String key = (String)i.next();
|
||||||
if (key.startsWith("file.") && key.endsWith(".dimensions")) {
|
if (key.startsWith("file.") && key.endsWith(".dimensions")) {
|
||||||
String name = key.substring("file.".length(), key.length() - ".dimensions".length());
|
String name = key.substring("file.".length(), key.length() - ".dimensions".length());
|
||||||
File f = new File(file, name);
|
if (!entryMap.containsKey(name)) {
|
||||||
String type = props.getProperty("file." + name + ".type", "image");
|
File f = new File(file, name);
|
||||||
FileEntry entry = new FileEntry(this, f, type);
|
String type = props.getProperty("file." + name + ".type", "image");
|
||||||
Thumbnail thumbnail = new Thumbnail(f, type);
|
FileEntry entry = new FileEntry(this, f, type);
|
||||||
entry.setCaption(props.getProperty("file." + name + ".caption"));
|
Thumbnail thumbnail = new Thumbnail(f, type);
|
||||||
Date fileDate = sdf.parse(props.getProperty("file." + name + ".captureDate"));
|
entry.setCaption(props.getProperty("file." + name + ".caption"));
|
||||||
entry.setDate(fileDate);
|
Date fileDate = sdf.parse(props.getProperty("file." + name + ".captureDate"));
|
||||||
thumbnail.setSize(new Dimension(props.getProperty("file." + name + ".dimensions")));
|
if (fileDate.before(oldest)) {
|
||||||
thumbnail.setOrientation(Integer.parseInt(props.getProperty("file." + name + ".orientation")));
|
oldest = fileDate;
|
||||||
thumbnail.setEtag(props.getProperty("file." + name + ".etag"));
|
}
|
||||||
entry.setThumbnail(thumbnail);
|
entry.setDate(fileDate);
|
||||||
boolean hidden = Boolean.parseBoolean(props.getProperty("file." + name + ".hidden"));
|
thumbnail.setSize(new Dimension(props.getProperty("file." + name + ".dimensions")));
|
||||||
if (!hidden) {
|
thumbnail.setOrientation(Integer.parseInt(props.getProperty("file." + name + ".orientation")));
|
||||||
children.add(entry);
|
thumbnail.setEtag(props.getProperty("file." + name + ".etag"));
|
||||||
if (name.equals(coverFileName)) {
|
entry.setThumbnail(thumbnail);
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
String duration = props.getProperty("file." + name + ".length");
|
|
||||||
if (duration != null) {
|
|
||||||
thumbnail.setDuration(duration);
|
|
||||||
}
|
}
|
||||||
} else if (key.startsWith("dir.") && !key.endsWith(".hidden") && !key.endsWith(".caption")) {
|
} else if (key.startsWith("dir.") && !key.endsWith(".hidden") && !key.endsWith(".caption")) {
|
||||||
String name = key.substring("dir.".length());
|
String name = key.substring("dir.".length());
|
||||||
boolean hidden = Boolean.parseBoolean(props.getProperty("dir." + name + ".hidden"));
|
boolean hidden = Boolean.parseBoolean(props.getProperty("dir." + name + ".hidden"));
|
||||||
if (!hidden) {
|
if (!hidden) {
|
||||||
DirectoryEntry dir = new DirectoryEntry(services, this, new File(file, name));
|
DirectoryEntry dir = new DirectoryEntry(services, this, new File(file, name));
|
||||||
if (name.equals(coverFileName)) {
|
if (name != null && name.equals(coverFileName)) {
|
||||||
setThumbnail(dir.getThumbnail());
|
setThumbnail(dir.getThumbnail());
|
||||||
}
|
}
|
||||||
Date fileDate = dir.getEarliest();
|
Date fileDate = dir.getEarliest();
|
||||||
dir.setDate(fileDate);
|
if (fileDate != null && fileDate.before(oldest)) {
|
||||||
|
oldest = fileDate;
|
||||||
|
}
|
||||||
dir.setCaption(props.getProperty("dir." + name + ".caption"));
|
dir.setCaption(props.getProperty("dir." + name + ".caption"));
|
||||||
if (!dir.getContents().isEmpty()) {
|
if (!dir.getContents().isEmpty()) {
|
||||||
children.add(dir);
|
children.add(dir);
|
||||||
|
|
@ -182,10 +197,7 @@ public class DirectoryEntry extends EntryWithChildren<Entry> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.earliest = children.stream()
|
this.earliest = oldest;
|
||||||
.map(Entry::getDate)
|
|
||||||
.min(Date::compareTo)
|
|
||||||
.orElse(new Date(file.lastModified()));
|
|
||||||
this.groupByYear = "year".equalsIgnoreCase(props.getProperty("group"));
|
this.groupByYear = "year".equalsIgnoreCase(props.getProperty("group"));
|
||||||
if (thumbnail == null && !children.isEmpty()) {
|
if (thumbnail == null && !children.isEmpty()) {
|
||||||
setThumbnail(children.get(0).getThumbnail());
|
setThumbnail(children.get(0).getThumbnail());
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.drew.metadata.Directory;
|
import com.drew.metadata.Directory;
|
||||||
import com.drew.metadata.Metadata;
|
import com.drew.metadata.Metadata;
|
||||||
|
import com.drew.metadata.MetadataException;
|
||||||
import com.drew.metadata.exif.ExifIFD0Directory;
|
import com.drew.metadata.exif.ExifIFD0Directory;
|
||||||
import com.drew.metadata.exif.ExifSubIFDDirectory;
|
import com.drew.metadata.exif.ExifSubIFDDirectory;
|
||||||
import com.drew.metadata.jpeg.JpegDirectory;
|
import com.drew.metadata.jpeg.JpegDirectory;
|
||||||
|
|
@ -59,9 +60,6 @@ public class DirectoryMetadataGenerator {
|
||||||
private void generateFileEntries(File dir, Properties props) throws IOException, InterruptedException {
|
private void generateFileEntries(File dir, Properties props) throws IOException, InterruptedException {
|
||||||
|
|
||||||
File[] files = dir.listFiles();
|
File[] files = dir.listFiles();
|
||||||
if (files == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (File f : files) {
|
for (File f : files) {
|
||||||
|
|
||||||
if (f.length() == 0) {
|
if (f.length() == 0) {
|
||||||
|
|
@ -136,6 +134,8 @@ public class DirectoryMetadataGenerator {
|
||||||
boolean hasOrientation = false;
|
boolean hasOrientation = false;
|
||||||
boolean hasDim = false;
|
boolean hasDim = false;
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss");
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd-HHmmss");
|
||||||
|
NumberFormat nf = new DecimalFormat("0");
|
||||||
|
|
||||||
|
|
||||||
Metadata metadata;
|
Metadata metadata;
|
||||||
try {
|
try {
|
||||||
|
|
@ -143,9 +143,6 @@ public class DirectoryMetadataGenerator {
|
||||||
} catch (ImageProcessingException e) {
|
} catch (ImageProcessingException e) {
|
||||||
// not a JPEG file
|
// not a JPEG file
|
||||||
return null;
|
return null;
|
||||||
} catch (RuntimeException e) {
|
|
||||||
log.error("Unexpected error processing file " + f.getAbsolutePath(), e);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Directory exifDirectory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
|
Directory exifDirectory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
|
||||||
|
|
@ -208,7 +205,7 @@ public class DirectoryMetadataGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Date getExifDate(Directory exifDirectory, int tagName) {
|
private Date getExifDate(Directory exifDirectory, int tagName) throws MetadataException {
|
||||||
|
|
||||||
String dateStr = exifDirectory.getString(tagName);
|
String dateStr = exifDirectory.getString(tagName);
|
||||||
if (" : : : : ".equals(dateStr)) {
|
if (" : : : : ".equals(dateStr)) {
|
||||||
|
|
@ -219,10 +216,16 @@ public class DirectoryMetadataGenerator {
|
||||||
|
|
||||||
private Dimension decodeImageForDimensions(File file) throws IOException {
|
private Dimension decodeImageForDimensions(File file) throws IOException {
|
||||||
|
|
||||||
ImageReader reader = getImageReader(file);
|
String suffix = null;
|
||||||
if (reader == null) {
|
String name = file.getName();
|
||||||
|
if (name.indexOf('.') > 0) {
|
||||||
|
suffix = name.substring(name.lastIndexOf('.') + 1);
|
||||||
|
}
|
||||||
|
Iterator<ImageReader> readers = ImageIO.getImageReadersBySuffix(suffix);
|
||||||
|
if (!readers.hasNext()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
ImageReader reader = readers.next();
|
||||||
ImageInputStream iis = ImageIO.createImageInputStream(file);
|
ImageInputStream iis = ImageIO.createImageInputStream(file);
|
||||||
reader.setInput(iis, true);
|
reader.setInput(iis, true);
|
||||||
ImageReadParam param = reader.getDefaultReadParam();
|
ImageReadParam param = reader.getDefaultReadParam();
|
||||||
|
|
@ -231,19 +234,6 @@ public class DirectoryMetadataGenerator {
|
||||||
return new Dimension(img.getWidth(), img.getHeight());
|
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 {
|
private Map<String, String> generatePdfProperties(File file) throws IOException {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public class EntryWithChildren<T extends Entry> extends Entry {
|
public class EntryWithChildren<T extends Entry> extends Entry {
|
||||||
|
|
||||||
protected List<T> children = new ArrayList<>();
|
protected List<T> children = new ArrayList<T>();
|
||||||
|
|
||||||
protected EntryWithChildren(Entry root) {
|
protected EntryWithChildren(Entry root) {
|
||||||
super(root);
|
super(root);
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,14 @@ import java.util.HashMap;
|
||||||
public class SearchResults extends EntryWithChildren<SearchEntry> {
|
public class SearchResults extends EntryWithChildren<SearchEntry> {
|
||||||
|
|
||||||
protected HashMap<File, SearchEntry> dupes = new HashMap<File, SearchEntry>();
|
protected HashMap<File, SearchEntry> dupes = new HashMap<File, SearchEntry>();
|
||||||
Comparator<SearchEntry> sort = (e1, e2) -> Integer.valueOf(e2.score).compareTo(e1.score);
|
Comparator<SearchEntry> sort = new Comparator<SearchEntry>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compare(SearchEntry e1, SearchEntry e2) {
|
||||||
|
|
||||||
|
return Integer.valueOf(e2.score).compareTo(e1.score);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
protected SearchResults(DirectoryEntry root) {
|
protected SearchResults(DirectoryEntry root) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package org.forkalsrud.album.video;
|
||||||
|
|
||||||
|
public interface EncodingProcessListener {
|
||||||
|
|
||||||
|
public abstract void chunkAvailable(int chunkNo);
|
||||||
|
|
||||||
|
public abstract void codingFinished(int lastCunkNo);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -474,7 +474,7 @@ public class FlvMetadata {
|
||||||
seconds += 3600 * Double.parseDouble(durationStr.substring(0, pm));
|
seconds += 3600 * Double.parseDouble(durationStr.substring(0, pm));
|
||||||
}
|
}
|
||||||
setDuration(seconds);
|
setDuration(seconds);
|
||||||
} else if (durationStr != null) {
|
} else {
|
||||||
setDuration(Double.parseDouble(durationStr));
|
setDuration(Double.parseDouble(durationStr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,8 @@ import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.LineNumberReader;
|
import java.io.LineNumberReader;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.forkalsrud.album.db.Chunk;
|
import org.forkalsrud.album.db.Chunk;
|
||||||
|
|
@ -23,12 +19,12 @@ import org.forkalsrud.album.web.PictureScaler;
|
||||||
|
|
||||||
public class MovieCoder {
|
public class MovieCoder {
|
||||||
|
|
||||||
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MovieCoder.class);
|
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(MovieCoder.class);
|
||||||
|
|
||||||
private String ffmpegExecutable;
|
private String ffmpegExecutable;
|
||||||
private String mplayerExecutable;
|
private String mplayerExecutable;
|
||||||
private final PictureScaler pictureScaler;
|
private PictureScaler pictureScaler;
|
||||||
private final HashMap<String, EncodingProcess> currentEncodings = new HashMap<>();
|
private HashMap<String, EncodingProcess> currentEncodings = new HashMap<String, EncodingProcess>();
|
||||||
|
|
||||||
public MovieCoder(PictureScaler pictureScaler) {
|
public MovieCoder(PictureScaler pictureScaler) {
|
||||||
this.pictureScaler = pictureScaler;
|
this.pictureScaler = pictureScaler;
|
||||||
|
|
@ -56,7 +52,7 @@ public class MovieCoder {
|
||||||
return temp;
|
return temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteTempDirectory(File dir) {
|
public void deleteTempDirectory(File dir) {
|
||||||
for (File sub : dir.listFiles()) {
|
for (File sub : dir.listFiles()) {
|
||||||
if (sub.isDirectory()) {
|
if (sub.isDirectory()) {
|
||||||
deleteTempDirectory(sub);
|
deleteTempDirectory(sub);
|
||||||
|
|
@ -78,7 +74,8 @@ public class MovieCoder {
|
||||||
p.getOutputStream().close();
|
p.getOutputStream().close();
|
||||||
log.debug(IOUtils.toString(p.getInputStream()));
|
log.debug(IOUtils.toString(p.getInputStream()));
|
||||||
p.waitFor();
|
p.waitFor();
|
||||||
return pictureScaler.scalePicture(frame, thumbnail, size);
|
CachedImage ci = pictureScaler.scalePicture(frame, thumbnail, size);
|
||||||
|
return ci;
|
||||||
} finally {
|
} finally {
|
||||||
deleteTempDirectory(tmpDir);
|
deleteTempDirectory(tmpDir);
|
||||||
}
|
}
|
||||||
|
|
@ -87,11 +84,11 @@ public class MovieCoder {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param file the file to encode
|
* @param file
|
||||||
* @param thumbnail
|
* @param thumbnail
|
||||||
* @param targetSize
|
* @param targetSize
|
||||||
* @param key
|
* @param key
|
||||||
* @return a handle to the encoding process
|
* @return
|
||||||
*/
|
*/
|
||||||
private synchronized EncodingProcess submitEncodingJob(File file,
|
private synchronized EncodingProcess submitEncodingJob(File file,
|
||||||
Thumbnail thumbnail, Dimension targetSize, String key, MovieDatabase movieDb) {
|
Thumbnail thumbnail, Dimension targetSize, String key, MovieDatabase movieDb) {
|
||||||
|
|
@ -108,19 +105,20 @@ public class MovieCoder {
|
||||||
class EncodingProcess implements Runnable, FlvFilter.FlvReceiver {
|
class EncodingProcess implements Runnable, FlvFilter.FlvReceiver {
|
||||||
|
|
||||||
private final int chunkSize = 4 * 65536;
|
private final int chunkSize = 4 * 65536;
|
||||||
private final File file;
|
private File file;
|
||||||
private final Dimension targetSize;
|
private Dimension targetSize;
|
||||||
|
private ArrayList<EncodingProcessListener> listeners = new ArrayList<EncodingProcessListener>();
|
||||||
private Chunk currentChunk = null;
|
private Chunk currentChunk = null;
|
||||||
private int chunkPos;
|
private int chunkPos;
|
||||||
private int remainingCapacity;
|
private int remainingCapacity;
|
||||||
private final AtomicInteger chunkInProgress;
|
private volatile int chunkInProgress = 0;
|
||||||
private volatile int chunkAvailable = 0;
|
private volatile int chunkAvailable = 0;
|
||||||
private final FlvFilter filter;
|
private FlvFilter filter;
|
||||||
private final String dbKey;
|
private String dbKey;
|
||||||
private final long fileTimestamp;
|
private long fileTimestamp;
|
||||||
private final int orientation;
|
private int orientation;
|
||||||
private volatile boolean done = false;
|
private volatile boolean done = false;
|
||||||
private final MovieDatabase movieDb;
|
private MovieDatabase movieDb;
|
||||||
|
|
||||||
|
|
||||||
public EncodingProcess(File file, Thumbnail thumbnail, Dimension size, MovieDatabase movieDb) {
|
public EncodingProcess(File file, Thumbnail thumbnail, Dimension size, MovieDatabase movieDb) {
|
||||||
|
|
@ -130,7 +128,6 @@ public class MovieCoder {
|
||||||
this.dbKey = key(file, targetSize);
|
this.dbKey = key(file, targetSize);
|
||||||
FlvMetadata extraMeta = new FlvMetadata();
|
FlvMetadata extraMeta = new FlvMetadata();
|
||||||
extraMeta.setDuration(thumbnail.getDuration());
|
extraMeta.setDuration(thumbnail.getDuration());
|
||||||
this.chunkInProgress = new AtomicInteger(0);
|
|
||||||
this.filter = new FlvFilter(this, extraMeta);
|
this.filter = new FlvFilter(this, extraMeta);
|
||||||
this.orientation = thumbnail.getOrientation();
|
this.orientation = thumbnail.getOrientation();
|
||||||
this.movieDb = movieDb;
|
this.movieDb = movieDb;
|
||||||
|
|
@ -141,7 +138,7 @@ public class MovieCoder {
|
||||||
* http://rob.opendot.cl/index.php/useful-stuff/ffmpeg-x264-encoding-guide/
|
* 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/
|
* 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
|
* and we want an output bitrate about 1 Mbit/sec
|
||||||
* we can do a one pass encoding like this:
|
* we can do a one pass encoding like this:
|
||||||
*
|
*
|
||||||
|
|
@ -177,7 +174,25 @@ public class MovieCoder {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
ProcessBuilder pb = createTranscoderProcess(vf);
|
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);
|
||||||
|
|
||||||
log.info(pb.command().toString());
|
log.info(pb.command().toString());
|
||||||
pb.redirectErrorStream(false);
|
pb.redirectErrorStream(false);
|
||||||
|
|
@ -214,34 +229,6 @@ 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.
|
* @return number of chunks available.
|
||||||
* -Integer.MAX_VALUE if something went very wrong
|
* -Integer.MAX_VALUE if something went very wrong
|
||||||
|
|
@ -270,9 +257,9 @@ public class MovieCoder {
|
||||||
int len = data.length;
|
int len = data.length;
|
||||||
Chunk chunk0 = new Chunk(fileTimestamp, len, 0);
|
Chunk chunk0 = new Chunk(fileTimestamp, len, 0);
|
||||||
chunk0.bits = data;
|
chunk0.bits = data;
|
||||||
log.info("Writing {} seq 0 ({}) {}", dbKey, chunkInProgress, data.length);
|
log.info("Writing " + dbKey + " seq 0 (" + chunkInProgress + ") " + data.length);
|
||||||
movieDb.store(dbKey, 0, chunk0);
|
movieDb.store(dbKey, 0, chunk0);
|
||||||
notifyListeners(chunkInProgress.get());
|
notifyListeners(chunkInProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -302,7 +289,7 @@ public class MovieCoder {
|
||||||
|
|
||||||
private void startNewChunk() {
|
private void startNewChunk() {
|
||||||
|
|
||||||
this.chunkInProgress.incrementAndGet();
|
this.chunkInProgress++;
|
||||||
this.currentChunk = new Chunk(fileTimestamp, chunkSize, 0);
|
this.currentChunk = new Chunk(fileTimestamp, chunkSize, 0);
|
||||||
this.chunkPos = 0;
|
this.chunkPos = 0;
|
||||||
this.remainingCapacity = chunkSize;
|
this.remainingCapacity = chunkSize;
|
||||||
|
|
@ -310,11 +297,11 @@ public class MovieCoder {
|
||||||
|
|
||||||
private void endChunk() {
|
private void endChunk() {
|
||||||
|
|
||||||
log.info("store chunk {}", chunkInProgress);
|
log.info("store chunk " + chunkInProgress);
|
||||||
movieDb.store(dbKey, chunkInProgress.get(), currentChunk);
|
movieDb.store(dbKey, chunkInProgress, currentChunk);
|
||||||
log.info("Writing {} seq {} ({}) {}", dbKey, chunkInProgress, chunkInProgress, currentChunk.bits.length);
|
log.info("Writing " + dbKey + " seq " + chunkInProgress + " (" + chunkInProgress + ") " + currentChunk.bits.length);
|
||||||
currentChunk = null;
|
currentChunk = null;
|
||||||
notifyListeners(chunkInProgress.get());
|
notifyListeners(chunkInProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void endLastChunk() {
|
private void endLastChunk() {
|
||||||
|
|
@ -325,16 +312,24 @@ public class MovieCoder {
|
||||||
// reallocate
|
// reallocate
|
||||||
Chunk last = new Chunk(fileTimestamp, chunkPos, 0);
|
Chunk last = new Chunk(fileTimestamp, chunkPos, 0);
|
||||||
System.arraycopy(currentChunk.bits, 0, last.bits, 0, chunkPos);
|
System.arraycopy(currentChunk.bits, 0, last.bits, 0, chunkPos);
|
||||||
movieDb.store(dbKey, chunkInProgress.get(), last);
|
movieDb.store(dbKey, chunkInProgress, last);
|
||||||
log.info("Writing {} seq {} ({}) {}", dbKey, chunkInProgress, chunkInProgress, last.bits.length);
|
log.info("Writing " + dbKey + " seq " + chunkInProgress + " (" + chunkInProgress + ") " + last.bits.length);
|
||||||
currentChunk = null;
|
currentChunk = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized void addListener(EncodingProcessListener videoStreamer) {
|
||||||
|
listeners.add(videoStreamer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void removeListener(VideoStreamer videoStreamer) {
|
||||||
|
listeners.remove(videoStreamer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ErrorStreamPumper implements Runnable {
|
class ErrorStreamPumper implements Runnable {
|
||||||
|
|
||||||
private final InputStream is;
|
private InputStream is;
|
||||||
private final String name;
|
private String name;
|
||||||
|
|
||||||
public ErrorStreamPumper(String processName, InputStream is) {
|
public ErrorStreamPumper(String processName, InputStream is) {
|
||||||
this.name = processName;
|
this.name = processName;
|
||||||
|
|
@ -343,12 +338,12 @@ public class MovieCoder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
org.slf4j.Logger diagnostic = org.slf4j.LoggerFactory.getLogger(this.name);
|
org.slf4j.Logger diag = org.slf4j.LoggerFactory.getLogger(this.name);
|
||||||
try {
|
try {
|
||||||
LineNumberReader lnr = new LineNumberReader(new InputStreamReader(is));
|
LineNumberReader lnr = new LineNumberReader(new InputStreamReader(is));
|
||||||
String line;
|
String line;
|
||||||
while ((line = lnr.readLine()) != null) {
|
while ((line = lnr.readLine()) != null) {
|
||||||
diagnostic.info(line);
|
diag.info(line);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("stderr?", e);
|
log.error("stderr?", e);
|
||||||
|
|
@ -387,7 +382,7 @@ public class MovieCoder {
|
||||||
// If the file has been modified since our last encoding job finished
|
// If the file has been modified since our last encoding job finished
|
||||||
if (chunk != null && ep == null) {
|
if (chunk != null && ep == null) {
|
||||||
if (chunk.timestamp != file.lastModified()) {
|
if (chunk.timestamp != file.lastModified()) {
|
||||||
log.info(" {} has changed so cache entry wil be refreshed", key);
|
log.info(" " + key + " has changed so cache entry wil be refreshed");
|
||||||
movieDb.delete(key);
|
movieDb.delete(key);
|
||||||
chunk = null;
|
chunk = null;
|
||||||
}
|
}
|
||||||
|
|
@ -403,11 +398,11 @@ public class MovieCoder {
|
||||||
class VideoStreamer {
|
class VideoStreamer {
|
||||||
|
|
||||||
private int chunkNo = 0;
|
private int chunkNo = 0;
|
||||||
private final EncodingProcess ep;
|
private EncodingProcess ep;
|
||||||
private Chunk chunk;
|
private Chunk chunk;
|
||||||
private final String key;
|
private String key;
|
||||||
private boolean done = false;
|
private boolean done = false;
|
||||||
private final MovieDatabase movieDb;
|
private MovieDatabase movieDb;
|
||||||
|
|
||||||
|
|
||||||
private VideoStreamer(String key, EncodingProcess ep, Chunk chunk0, MovieDatabase movieDb) {
|
private VideoStreamer(String key, EncodingProcess ep, Chunk chunk0, MovieDatabase movieDb) {
|
||||||
|
|
@ -429,11 +424,11 @@ public class MovieCoder {
|
||||||
while (!done) {
|
while (!done) {
|
||||||
|
|
||||||
if (chunk == null) {
|
if (chunk == null) {
|
||||||
log.info("Looking for {} - {}", key, chunkNo);
|
log.info("Looking for " + key + " - " + chunkNo);
|
||||||
chunk = movieDb.load(key, chunkNo);
|
chunk = movieDb.load(key, chunkNo);
|
||||||
}
|
}
|
||||||
if (chunk != null) {
|
if (chunk != null) {
|
||||||
log.info("Sending {}, {} bytes", chunkNo, chunk.bits.length);
|
log.info("Sending " + chunkNo + ", " + chunk.bits.length + " bytes");
|
||||||
out.write(chunk.bits);
|
out.write(chunk.bits);
|
||||||
chunk = null;
|
chunk = null;
|
||||||
chunkNo++;
|
chunkNo++;
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,18 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.apache.log4j.PropertyConfigurator;
|
import org.apache.log4j.PropertyConfigurator;
|
||||||
import org.forkalsrud.album.db.DirectoryDatabase;
|
import org.forkalsrud.album.db.DirectoryDatabase;
|
||||||
import org.forkalsrud.album.db.DirectoryProps;
|
import org.forkalsrud.album.db.DirectoryProps;
|
||||||
import org.forkalsrud.album.db.MovieDatabase;
|
import org.forkalsrud.album.db.MovieDatabase;
|
||||||
import org.forkalsrud.album.db.ThumbnailDatabase;
|
import org.forkalsrud.album.db.ThumbnailDatabase;
|
||||||
import org.forkalsrud.album.exif.*;
|
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.video.MovieCoder;
|
import org.forkalsrud.album.video.MovieCoder;
|
||||||
import org.springframework.web.util.HtmlUtils;
|
import org.springframework.web.util.HtmlUtils;
|
||||||
|
|
||||||
|
|
@ -37,7 +42,7 @@ import com.sleepycat.je.EnvironmentConfig;
|
||||||
public class AlbumServlet
|
public class AlbumServlet
|
||||||
extends HttpServlet
|
extends HttpServlet
|
||||||
{
|
{
|
||||||
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AlbumServlet.class);
|
private static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(AlbumServlet.class);
|
||||||
|
|
||||||
static void addDummyLoggerFor(String... names) {
|
static void addDummyLoggerFor(String... names) {
|
||||||
for (String name : names)
|
for (String name : names)
|
||||||
|
|
@ -73,6 +78,7 @@ public class AlbumServlet
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
hh = ll.getHandlers();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
@ -95,9 +101,9 @@ public class AlbumServlet
|
||||||
* maps files to URIs relative to servlet, e.g. /home/joe/photos/holiday/arrival.jpg -> /photos/holiday/arrival.jpg
|
* 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
|
* assuming base is /home/joe/photos
|
||||||
*/
|
*/
|
||||||
public static class Mapper {
|
public class Mapper {
|
||||||
|
|
||||||
private final File base;
|
private File base;
|
||||||
|
|
||||||
public Mapper(File base) {
|
public Mapper(File base) {
|
||||||
this.base = base;
|
this.base = base;
|
||||||
|
|
@ -108,7 +114,7 @@ public class AlbumServlet
|
||||||
return appendFile(buf, file).toString();
|
return appendFile(buf, file).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private StringBuilder appendFile(StringBuilder buf, File file) {
|
StringBuilder appendFile(StringBuilder buf, File file) {
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
@ -118,6 +124,16 @@ public class AlbumServlet
|
||||||
return appendFile(buf, file.getParentFile()).append('/').append(file.getName());
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -128,7 +144,7 @@ public class AlbumServlet
|
||||||
String basePrefix;
|
String basePrefix;
|
||||||
DirectoryEntryFactory dirEntryFactory;
|
DirectoryEntryFactory dirEntryFactory;
|
||||||
|
|
||||||
private final Environment environment;
|
private Environment environment;
|
||||||
ThumbnailDatabase thumbDb;
|
ThumbnailDatabase thumbDb;
|
||||||
DirectoryDatabase dirDb;
|
DirectoryDatabase dirDb;
|
||||||
MovieDatabase movieDb;
|
MovieDatabase movieDb;
|
||||||
|
|
@ -209,8 +225,8 @@ public class AlbumServlet
|
||||||
res.setContentType("text/html");
|
res.setContentType("text/html");
|
||||||
req.setAttribute("search", query);
|
req.setAttribute("search", query);
|
||||||
req.setAttribute("entry", results);
|
req.setAttribute("entry", results);
|
||||||
req.setAttribute("thmb", 250);
|
req.setAttribute("thmb", new Integer(250));
|
||||||
req.setAttribute("full", 800);
|
req.setAttribute("full", new Integer(800));
|
||||||
RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/velocity/photo.vm");
|
RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/velocity/photo.vm");
|
||||||
rd.forward(req, res);
|
rd.forward(req, res);
|
||||||
}
|
}
|
||||||
|
|
@ -219,7 +235,7 @@ public class AlbumServlet
|
||||||
void handlePhoto(HttpServletRequest req, HttpServletResponse res, FileEntry entry) throws Exception {
|
void handlePhoto(HttpServletRequest req, HttpServletResponse res, FileEntry entry) throws Exception {
|
||||||
res.setContentType("text/html");
|
res.setContentType("text/html");
|
||||||
req.setAttribute("entry", entry);
|
req.setAttribute("entry", entry);
|
||||||
req.setAttribute("thmb", 800);
|
req.setAttribute("thmb", new Integer(800));
|
||||||
RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/velocity/photo.vm");
|
RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/velocity/photo.vm");
|
||||||
rd.forward(req, res);
|
rd.forward(req, res);
|
||||||
}
|
}
|
||||||
|
|
@ -227,20 +243,20 @@ public class AlbumServlet
|
||||||
void handleAlbum(HttpServletRequest req, HttpServletResponse res, DirectoryEntry entry) throws Exception {
|
void handleAlbum(HttpServletRequest req, HttpServletResponse res, DirectoryEntry entry) throws Exception {
|
||||||
res.setContentType("text/html");
|
res.setContentType("text/html");
|
||||||
req.setAttribute("entry", entry);
|
req.setAttribute("entry", entry);
|
||||||
req.setAttribute("thmb", 250);
|
req.setAttribute("thmb", new Integer(250));
|
||||||
req.setAttribute("full", 800);
|
req.setAttribute("full", new Integer(800));
|
||||||
req.setAttribute("D", "$");
|
req.setAttribute("D", "$");
|
||||||
RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/ng.html");
|
RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/ng.html");
|
||||||
rd.forward(req, res);
|
rd.forward(req, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleMovieFrame(HttpServletRequest req, HttpServletResponse res, FileEntry entry) {
|
void handleMovieFrame(HttpServletRequest req, HttpServletResponse res, FileEntry entry) throws Exception {
|
||||||
|
|
||||||
File file = entry.getPath();
|
File file = entry.getPath();
|
||||||
if (notModified(req, file)) {
|
if (notModified(req, file)) {
|
||||||
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||||
res.setDateHeader("Expires", System.currentTimeMillis() + (30 * 24 * 3600 * 1000L)); // 30 days
|
res.setDateHeader("Expires", System.currentTimeMillis() + (30 * 24 * 3600 * 1000L)); // 30 days
|
||||||
log.info("{} not modified (based on date)", file.getName());
|
log.info(file.getName() + " not modified (based on date)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int secondNo = 3;
|
int secondNo = 3;
|
||||||
|
|
@ -255,7 +271,7 @@ public class AlbumServlet
|
||||||
if (cimg.lastModified >= fileTs) {
|
if (cimg.lastModified >= fileTs) {
|
||||||
// log.info("cache hit on " + key);
|
// log.info("cache hit on " + key);
|
||||||
} else {
|
} else {
|
||||||
log.info(" {} has changed so cache entry wil be refreshed", key);
|
log.info(" " + key + " has changed so cache entry wil be refreshed");
|
||||||
cimg = null;
|
cimg = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -263,7 +279,7 @@ public class AlbumServlet
|
||||||
try {
|
try {
|
||||||
cimg = movieCoder.extractFrame(file, secondNo, entry.getThumbnail(), size);
|
cimg = movieCoder.extractFrame(file, secondNo, entry.getThumbnail(), size);
|
||||||
thumbDb.store(key, cimg);
|
thumbDb.store(key, cimg);
|
||||||
log.info(" {} added to the cache with size {} -- now {} entries", key, cimg.bits.length, thumbDb.size());
|
log.info(" " + key + " added to the cache with size " + cimg.bits.length + " -- now " + thumbDb.size() + " entries");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
//e.fillInStackTrace();
|
//e.fillInStackTrace();
|
||||||
throw new RuntimeException("sadness", e);
|
throw new RuntimeException("sadness", e);
|
||||||
|
|
@ -287,29 +303,11 @@ public class AlbumServlet
|
||||||
if (notModified(req, file)) {
|
if (notModified(req, file)) {
|
||||||
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||||
res.setDateHeader("Expires", System.currentTimeMillis() + (30 * 24 * 3600 * 1000L)); // 30 days
|
res.setDateHeader("Expires", System.currentTimeMillis() + (30 * 24 * 3600 * 1000L)); // 30 days
|
||||||
log.info("{} not modified (based on date)", file.getName());
|
log.info(file.getName() + " not modified (based on date)");
|
||||||
return;
|
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 {
|
try {
|
||||||
|
String size = req.getParameter("size");
|
||||||
res.setStatus(HttpServletResponse.SC_OK);
|
res.setStatus(HttpServletResponse.SC_OK);
|
||||||
res.setDateHeader("Last-Modified", entry.getPath().lastModified());
|
res.setDateHeader("Last-Modified", entry.getPath().lastModified());
|
||||||
res.setDateHeader("Expires", System.currentTimeMillis() + (30 * 24 * 3600 * 1000L)); // 30 days
|
res.setDateHeader("Expires", System.currentTimeMillis() + (30 * 24 * 3600 * 1000L)); // 30 days
|
||||||
|
|
@ -325,18 +323,6 @@ public class AlbumServlet
|
||||||
|
|
||||||
void handleJson(HttpServletRequest req, HttpServletResponse res, DirectoryEntry entry) {
|
void handleJson(HttpServletRequest req, HttpServletResponse res, DirectoryEntry entry) {
|
||||||
try {
|
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);
|
Mapper mapper = new Mapper(base);
|
||||||
res.setContentType("application/json");
|
res.setContentType("application/json");
|
||||||
res.setCharacterEncoding("UTF-8");
|
res.setCharacterEncoding("UTF-8");
|
||||||
|
|
@ -345,23 +331,29 @@ public class AlbumServlet
|
||||||
ObjectNode root = json.createObjectNode();
|
ObjectNode root = json.createObjectNode();
|
||||||
root.put("name", entry.getName());
|
root.put("name", entry.getName());
|
||||||
|
|
||||||
addDir(root, entry.parent(), "parent", mapper);
|
if (entry.parent() != null && "dir".equals(entry.parent().getType())) {
|
||||||
addDir(root, entry.prev(), "prev", mapper);
|
root.put("parent", mapper.map(entry.parent().getPath()));
|
||||||
addDir(root, entry.next(), "next", mapper);
|
}
|
||||||
|
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()));
|
||||||
|
}
|
||||||
if (entry.groupByYear()) {
|
if (entry.groupByYear()) {
|
||||||
root.put("groupPrefix", 4);
|
root.put("groupPrefix", 4);
|
||||||
}
|
}
|
||||||
ArrayNode list = root.putArray("contents");
|
ArrayNode contents = root.putArray("contents");
|
||||||
for (Entry e : contents.getContents()) {
|
for (Entry e : entry.getContents()) {
|
||||||
try {
|
try {
|
||||||
ObjectNode item = list.addObject();
|
ObjectNode item = contents.addObject();
|
||||||
item.put("name", e.getName());
|
item.put("name", e.getName());
|
||||||
item.put("type", e.getType());
|
item.put("type", e.getType());
|
||||||
if ("dir".equals(e.getType())) {
|
if ("dir".equals(e.getType())) {
|
||||||
if (e.getEarliest() != null) {
|
DirectoryEntry de = (DirectoryEntry)e;
|
||||||
|
if (de.getEarliest() != null) {
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
|
||||||
item.put("earliest", sdf.format(e.getEarliest()));
|
item.put("earliest", sdf.format(de.getEarliest()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Thumbnail thumb = e.getThumbnail();
|
Thumbnail thumb = e.getThumbnail();
|
||||||
|
|
@ -385,14 +377,6 @@ 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) {
|
void handleCache(HttpServletRequest req, HttpServletResponse res, DirectoryEntry entry) {
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
res.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
res.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||||
|
|
@ -435,7 +419,7 @@ public class AlbumServlet
|
||||||
}
|
}
|
||||||
res.setContentType("text/html");
|
res.setContentType("text/html");
|
||||||
req.setAttribute("entry", entry);
|
req.setAttribute("entry", entry);
|
||||||
req.setAttribute("thmb", 640);
|
req.setAttribute("thmb", new Integer(640));
|
||||||
RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/velocity/edit.vm");
|
RequestDispatcher rd = req.getRequestDispatcher("/WEB-INF/velocity/edit.vm");
|
||||||
rd.forward(req, res);
|
rd.forward(req, res);
|
||||||
}
|
}
|
||||||
|
|
@ -449,14 +433,14 @@ public class AlbumServlet
|
||||||
if (notModified(req, file)) {
|
if (notModified(req, file)) {
|
||||||
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||||
res.setDateHeader("Expires", System.currentTimeMillis() + (30 * 24 * 3600 * 1000L)); // 30 days
|
res.setDateHeader("Expires", System.currentTimeMillis() + (30 * 24 * 3600 * 1000L)); // 30 days
|
||||||
log.info("{} not modified (based on date)", file.getName());
|
log.info(file.getName() + " not modified (based on date)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String fileEtag = thumbnail.getEtag() + "-" + size;
|
String fileEtag = thumbnail.getEtag() + "-" + size;
|
||||||
if (etagMatches(req, fileEtag)) {
|
if (etagMatches(req, fileEtag)) {
|
||||||
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
|
||||||
res.setDateHeader("Expires", System.currentTimeMillis() + (30 * 24 * 3600 * 1000L)); // 30 days
|
res.setDateHeader("Expires", System.currentTimeMillis() + (30 * 24 * 3600 * 1000L)); // 30 days
|
||||||
log.info("{} not modified (based on etag)", file.getName());
|
log.info(file.getName() + " not modified (based on etag)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -467,15 +451,14 @@ public class AlbumServlet
|
||||||
if (cimg.lastModified == file.lastModified()) {
|
if (cimg.lastModified == file.lastModified()) {
|
||||||
// log.info("cache hit on " + key);
|
// log.info("cache hit on " + key);
|
||||||
} else {
|
} else {
|
||||||
log.info(" {} has changed so cache entry wil be refreshed", key);
|
log.info(" " + key + " has changed so cache entry wil be refreshed");
|
||||||
cimg = null;
|
cimg = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cimg == null) {
|
if (cimg == null) {
|
||||||
cimg = pictureScaler.scalePicture(file, thumbnail, size);
|
cimg = pictureScaler.scalePicture(file, thumbnail, size);
|
||||||
thumbDb.store(key, cimg);
|
thumbDb.store(key, cimg);
|
||||||
int byteSize = cimg.bits != null ? cimg.bits.length : 0;
|
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, byteSize, thumbDb.size());
|
|
||||||
}
|
}
|
||||||
res.setStatus(HttpServletResponse.SC_OK);
|
res.setStatus(HttpServletResponse.SC_OK);
|
||||||
res.setDateHeader("Last-Modified", file.lastModified());
|
res.setDateHeader("Last-Modified", file.lastModified());
|
||||||
|
|
@ -489,7 +472,7 @@ public class AlbumServlet
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Map<String, Root> roots = new HashMap<>();
|
Map<String, Root> roots = new HashMap<String, Root>();
|
||||||
|
|
||||||
|
|
||||||
PictureScaler pictureScaler;
|
PictureScaler pictureScaler;
|
||||||
|
|
@ -576,7 +559,7 @@ public class AlbumServlet
|
||||||
@Override
|
@Override
|
||||||
public void destroy() {
|
public void destroy() {
|
||||||
log.info("Shutting down Album");
|
log.info("Shutting down Album");
|
||||||
roots.values().forEach(Root::close);
|
roots.values().forEach(r -> r.close());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -615,25 +598,19 @@ public class AlbumServlet
|
||||||
|
|
||||||
// help the user get to the top level page
|
// help the user get to the top level page
|
||||||
if (pathInfo == null || "/".equals(pathInfo)) {
|
if (pathInfo == null || "/".equals(pathInfo)) {
|
||||||
String rootPath = roots.values().stream()
|
Root root = roots.values().stream().findFirst().orElse(null);
|
||||||
.findFirst()
|
String u = req.getContextPath() + "/album/" + root.getName() + ".album";
|
||||||
.map(r -> req.getContextPath() + "/album/" + r.getName() + ".album")
|
res.sendRedirect(u);
|
||||||
.orElse(null);
|
return;
|
||||||
if (rootPath != null) {
|
|
||||||
res.sendRedirect(rootPath);
|
|
||||||
} else {
|
|
||||||
res.setStatus(404);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("/_roots.json".equals(pathInfo)) {
|
if ("/_roots.json".equals(pathInfo)) {
|
||||||
res.setContentType("application/json");
|
res.setContentType("application/json");
|
||||||
res.setCharacterEncoding("UTF-8");
|
res.setCharacterEncoding("UTF-8");
|
||||||
|
|
||||||
ObjectMapper json = new ObjectMapper();
|
ObjectMapper json = new ObjectMapper();
|
||||||
ArrayNode arr = json.createArrayNode();
|
ArrayNode arr = json.createArrayNode();
|
||||||
roots.values().stream().map(Root::getName).forEach(arr::add);
|
roots.values().stream().map(r -> r.getName()).forEach(arr::add);
|
||||||
json.writeValue(res.getOutputStream(), arr);
|
json.writeValue(res.getOutputStream(), arr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -713,9 +690,8 @@ public class AlbumServlet
|
||||||
}
|
}
|
||||||
|
|
||||||
String size = req.getParameter("size");
|
String size = req.getParameter("size");
|
||||||
if ("max".equals(size)) {
|
if (size != null) {
|
||||||
getServletContext().getNamedDispatcher("file").forward(req, res);
|
|
||||||
} else if (size != null) {
|
|
||||||
FileEntry e = (FileEntry)root.resolve(file);
|
FileEntry e = (FileEntry)root.resolve(file);
|
||||||
root.procesScaledImageRequest(req, res, file, e.getThumbnail(), size);
|
root.procesScaledImageRequest(req, res, file, e.getThumbnail(), size);
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
|
|
@ -25,7 +26,10 @@ import javax.imageio.ImageWriter;
|
||||||
import javax.imageio.stream.ImageOutputStream;
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
|
|
||||||
import org.apache.pdfbox.Loader;
|
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.RandomAccessReadBufferedFile;
|
||||||
|
import org.apache.pdfbox.io.RandomAccessReadMemoryMappedFile;
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
import org.apache.pdfbox.pdmodel.PDPage;
|
import org.apache.pdfbox.pdmodel.PDPage;
|
||||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||||
|
|
@ -43,10 +47,9 @@ public class PictureScaler {
|
||||||
|
|
||||||
|
|
||||||
public 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);
|
executor = Executors.newFixedThreadPool(4);
|
||||||
outstandingRequests = new HashMap<>();
|
outstandingRequests = new HashMap<String, PictureRequest>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -116,8 +119,19 @@ public class PictureScaler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Comparator<PictureRequest> createPriorityComparator() {
|
||||||
|
|
||||||
public CachedImage scalePicture(File file, Thumbnail thumbnail, String size) {
|
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) {
|
||||||
String key = file.getPath() + ":" + size;
|
String key = file.getPath() + ":" + size;
|
||||||
return scalePicture(key, file, thumbnail, size);
|
return scalePicture(key, file, thumbnail, size);
|
||||||
}
|
}
|
||||||
|
|
@ -167,13 +181,6 @@ public class PictureScaler {
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferedImage img = ImageIO.read(file);
|
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
|
// The orientation is about flipping and rotating. Here is what an 'F' looks like
|
||||||
// on pictures with each orientation.
|
// on pictures with each orientation.
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,13 @@
|
||||||
<title>album</title>
|
<title>album</title>
|
||||||
|
|
||||||
<script type="text/javascript" src="/photo/assets/jquery/jquery-3.7.1.min.js"></script>
|
<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.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.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/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" />
|
<link rel="stylesheet" href="/photo/assets/fancybox/jquery.fancybox-1.3.1.css" type="text/css" media="screen" />
|
||||||
|
|
||||||
|
|
@ -26,6 +30,7 @@
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
form {
|
form {
|
||||||
|
float: right;
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
h2 {
|
h2 {
|
||||||
|
|
@ -57,7 +62,7 @@
|
||||||
max-width: 275px;
|
max-width: 275px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
img.imagepic, img.moviepic {
|
img.imagepic {
|
||||||
box-shadow: 5px 5px 5px #777;
|
box-shadow: 5px 5px 5px #777;
|
||||||
-webkit-box-shadow: 5px 5px 5px #777;
|
-webkit-box-shadow: 5px 5px 5px #777;
|
||||||
-moz-box-shadow: 5px 5px 5px #777;
|
-moz-box-shadow: 5px 5px 5px #777;
|
||||||
|
|
@ -76,26 +81,17 @@
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
#fancybox-left, #fancybox-right {
|
#fancybox-left, #fancybox-right {
|
||||||
bottom: 70px;
|
bottom: 30px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="text/javascript" src="/photo/assets/render.js"></script>
|
<script type="text/javascript" src="/photo/assets/render.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<form id="search" action="/photo/album/search" method="get"><!--
|
<form id="search" action="/photo/album/search" method="get"><input name="q" value=""></form>
|
||||||
--><h1 id="titleBar" style="height: 32;"><!--
|
<h1 id="titleBar" style="height: 32;"><!--
|
||||||
--><img id="arrowLeft" class="nav" width="26" height="32" src="/photo/assets/left-inactive.png"><!--
|
--><img 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 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"><!--
|
--><img class="nav" width="26" height="32" src="/photo/assets/right-inactive.png"> </h1>
|
||||||
--> <!--
|
|
||||||
--><span id="entryName"></span><!--
|
|
||||||
--> <!--
|
|
||||||
--><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/>
|
<hr/>
|
||||||
<div id="container"></div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,7 @@
|
||||||
|
|
||||||
selectedIndex = 0, selectedOpts = {}, selectedArray = [], currentIndex = 0, currentOpts = {}, currentArray = [],
|
selectedIndex = 0, selectedOpts = {}, selectedArray = [], currentIndex = 0, currentOpts = {}, currentArray = [],
|
||||||
|
|
||||||
ajaxLoader = null, imgPreloader = new Image(),
|
ajaxLoader = null, imgPreloader = new Image(), imgRegExp = /\.(jpg|gif|png|bmp|jpeg)(.*)?$/i, swfRegExp = /[^\.]\.(swf)\s*$/i,
|
||||||
imgRegExp = /\.(jpg|gif|png|bmp|jpeg)(.*)?$/i,
|
|
||||||
movieRegExp = /\.(mov)(.*)?$/i,
|
|
||||||
swfRegExp = /[^\.]\.(swf)\s*$/i,
|
|
||||||
|
|
||||||
loadingTimer, loadingFrame = 1,
|
loadingTimer, loadingFrame = 1,
|
||||||
|
|
||||||
|
|
@ -243,7 +240,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
_finish = function () {
|
_finish = function () {
|
||||||
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')));
|
inner.css('overflow', (currentOpts.scrolling == 'auto' ? (currentOpts.type == 'image' || currentOpts.type == 'iframe' || currentOpts.type == 'swf' ? 'hidden' : 'auto') : (currentOpts.scrolling == 'yes' ? 'auto' : 'visible')));
|
||||||
|
|
||||||
if (!$.support.opacity) {
|
if (!$.support.opacity) {
|
||||||
inner.get(0).style.removeProperty('filter');
|
inner.get(0).style.removeProperty('filter');
|
||||||
|
|
@ -553,9 +550,6 @@
|
||||||
if (href.match(imgRegExp)) {
|
if (href.match(imgRegExp)) {
|
||||||
type = 'image';
|
type = 'image';
|
||||||
|
|
||||||
} else if (href.match(movieRegExp)) {
|
|
||||||
type = 'movie';
|
|
||||||
|
|
||||||
} else if (href.match(swfRegExp)) {
|
} else if (href.match(swfRegExp)) {
|
||||||
type = 'swf';
|
type = 'swf';
|
||||||
|
|
||||||
|
|
@ -577,7 +571,7 @@
|
||||||
selectedOpts.href = href;
|
selectedOpts.href = href;
|
||||||
selectedOpts.title = title;
|
selectedOpts.title = title;
|
||||||
|
|
||||||
if (selectedOpts.autoDimensions && selectedOpts.type !== 'iframe' && selectedOpts.type !== 'swf' && selectedOpts.type !== 'movie') {
|
if (selectedOpts.autoDimensions && selectedOpts.type !== 'iframe' && selectedOpts.type !== 'swf') {
|
||||||
selectedOpts.width = 'auto';
|
selectedOpts.width = 'auto';
|
||||||
selectedOpts.height = 'auto';
|
selectedOpts.height = 'auto';
|
||||||
}
|
}
|
||||||
|
|
@ -642,13 +636,6 @@
|
||||||
|
|
||||||
break;
|
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':
|
case 'swf':
|
||||||
str = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="' + selectedOpts.width + '" height="' + selectedOpts.height + '"><param name="movie" value="' + href + '"></param>';
|
str = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="' + selectedOpts.width + '" height="' + selectedOpts.height + '"><param name="movie" value="' + href + '"></param>';
|
||||||
emb = '';
|
emb = '';
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
|
|
||||||
function formatCaption(title, currentArray, currentIndex, currentOpts) {
|
function formatTitle(title, currentArray, currentIndex, currentOpts) {
|
||||||
var captionElement = document.getElementById(title);
|
var captionElement = document.getElementById(title);
|
||||||
return captionElement.innerHTML;
|
return captionElement.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
@ -15,19 +15,8 @@ $(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) {
|
function renderGrid(data, textStatus) {
|
||||||
|
|
||||||
var container = $("#container");
|
|
||||||
container.empty();
|
|
||||||
$("#name").text(data.name);
|
$("#name").text(data.name);
|
||||||
document.title = data.name;
|
document.title = data.name;
|
||||||
|
|
||||||
|
|
@ -36,96 +25,89 @@ $(function() {
|
||||||
var movieSize = 640;
|
var movieSize = 640;
|
||||||
var prevName = null, thisName;
|
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) {
|
$.each(data.contents, function(idx, entry) {
|
||||||
|
|
||||||
if (data.groupPrefix && entry.type == "dir") {
|
if (data.groupPrefix && entry.type == "dir") {
|
||||||
thisName = entry.earliest;
|
thisName = entry.earliest;
|
||||||
if (prevName != null && prevName != thisName) {
|
if (prevName != null && prevName != thisName) {
|
||||||
$("<hr/>").appendTo(container);
|
$("<hr/>").appendTo("body");
|
||||||
prevName = null;
|
prevName = null;
|
||||||
}
|
}
|
||||||
if (prevName == null) {
|
if (prevName == null) {
|
||||||
$("<h2></h2>").text(thisName).appendTo(container);
|
$("<h2></h2>").text(thisName).appendTo("body");
|
||||||
prevName = thisName;
|
prevName = thisName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var dim = scale(entry.width, entry.height, thmb);
|
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"
|
var gridDiv = $("<div class=\"grid\">\n"
|
||||||
+ " <span class=\"name\">" + entry.name + "</span><br/>\n"
|
+ " <span class=\"name\">" + entry.name + "</span><br/>\n"
|
||||||
+ " <div class=\"imgborder\"><a class=\"ss\" id=\"ent" + idx + "\" href=\"" + href + "\" title=\"" + entry.name + "\">"
|
+ " <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=\"" + poster + "?size=" + thmb + "\" border=\"0\" width=\"" + dim.w + "\" height=\"" + dim.h + "\"/></a></div>\n"
|
+ "<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>\n");
|
+ "</div>\n");
|
||||||
|
|
||||||
var captionP = $("<p class=\"caption\"></p>\n");
|
var captionP = $("<p id=\"" + entry.name + "\" class=\"caption\"></p>\n");
|
||||||
captionP.attr("id", entry.name);
|
|
||||||
if (entry.caption) {
|
if (entry.caption) {
|
||||||
captionP.text(entry.caption);
|
captionP.text(entry.caption);
|
||||||
}
|
}
|
||||||
captionP.appendTo(gridDiv);
|
captionP.appendTo(gridDiv);
|
||||||
|
|
||||||
gridDiv.appendTo(container);
|
gridDiv.appendTo('body');
|
||||||
|
|
||||||
switch (entry.type) {
|
switch (entry.type) {
|
||||||
case "movie":
|
case "movie":
|
||||||
case "image":
|
var size = scale(entry.width, entry.height, movieSize);
|
||||||
var largeDim = scale(entry.width, entry.height, picSize);
|
var href = "/photo/album" + escape(entry.path) + ".movie?size=" + movieSize;
|
||||||
var options = {
|
$("#ent" + idx).attr("rel", "album").attr("href", href).fancybox({
|
||||||
'titlePosition' : 'inside',
|
'titlePosition' : 'inside',
|
||||||
'transitionIn' : 'elastic',
|
'transitionIn' : 'elastic',
|
||||||
'transitionOut' : 'elastic',
|
'transitionOut' : 'elastic',
|
||||||
'easingIn' : 'easeInQuad',
|
'easingIn' : 'easeInQuad',
|
||||||
'easingOut' : 'easeOutQuad',
|
'easingOut' : 'easeOutQuad',
|
||||||
'titleFormat' : formatCaption,
|
'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({
|
||||||
|
'titlePosition' : 'inside',
|
||||||
|
'transitionIn' : 'elastic',
|
||||||
|
'transitionOut' : 'elastic',
|
||||||
|
'easingIn' : 'easeInQuad',
|
||||||
|
'easingOut' : 'easeOutQuad',
|
||||||
|
'titleFormat' : formatTitle,
|
||||||
'width' : largeDim.w,
|
'width' : largeDim.w,
|
||||||
'height' : largeDim.h,
|
'height' : largeDim.h,
|
||||||
'type' : entry.type,
|
'type' : "image",
|
||||||
'href' : href
|
'href' : $("#ent" + idx).href
|
||||||
};
|
});
|
||||||
if (entry.thumbtype == "movie") {
|
|
||||||
options['poster'] = poster + "?size=" + largeDim.w + "w";
|
|
||||||
}
|
|
||||||
$("#ent" + idx).attr("rel", "album").fancybox(options);
|
|
||||||
break;
|
break;
|
||||||
case "dir":
|
case "dir":
|
||||||
$("#ent" + idx).attr("href", location.pathname.replace(/\.album?($|\?)/, "/" + entry.name + ".album"));
|
$("#ent" + idx).attr("href", location.pathname.replace(/\.album?($|\?)/, "/" + entry.name + ".album"));
|
||||||
|
|
@ -133,49 +115,43 @@ $(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\"/>");
|
||||||
var selectedImg = window.location.search;
|
if (destination) {
|
||||||
var selectedPos = undefined;
|
return $("<a></a>").attr("href", "/photo/album" + destination + ".album").append(imgElement);
|
||||||
if (/^\?focus=/.test(selectedImg)) {
|
} else {
|
||||||
selectedImg = selectedImg.substring('?focus='.length);
|
return imgElement;
|
||||||
$("a.ss").each(function(index) {
|
|
||||||
if (selectedImg == $(this).attr("title")) {
|
|
||||||
selectedPos = index;
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedPos !== undefined) {
|
$("#titleBar").html("").append(buildButton("left", data.prev)).append(buildButton("up", data.parent)).append(buildButton("right", data.next)).append(" " + data.name);
|
||||||
$("#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").on("submit", function () {
|
|
||||||
var searchStr = "?q=" + encUri($("#searchBox").val());
|
$("#search").each(function (id, el) {
|
||||||
history.pushState({ }, null, searchStr);
|
el.action = location.pathname.replace(/\.album(\?|$)/, '.search');
|
||||||
$.getJSON(location.pathname.replace(/\.album(\?|$)/, '.json') + searchStr, renderGrid);
|
|
||||||
return false;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function resetSearch() {
|
$.getJSON(location.pathname.replace(/\.album(\?|$)/, '.json'), renderGrid);
|
||||||
$("#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);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue