Compare commits
10 commits
9851784fde
...
9dd452fe36
| Author | SHA1 | Date | |
|---|---|---|---|
| 9dd452fe36 | |||
| 23fb5e4c68 | |||
| efdd2b6ea9 | |||
| 958c02e670 | |||
| 7df5a06cd1 | |||
| 05859a2e58 | |||
| 3f94629953 | |||
| 4e662b92ff | |||
| 2b8f7e474a | |||
| da75204ded |
17 changed files with 1460 additions and 1386 deletions
|
|
@ -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
18
pom.xml
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
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));
|
||||
}
|
||||
setDuration(seconds);
|
||||
} else {
|
||||
} else if (durationStr != null) {
|
||||
setDuration(Double.parseDouble(durationStr));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"> </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"><!--
|
||||
--> <!--
|
||||
--><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/>
|
||||
<div id="container"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -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 = '';
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
||||
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue