Browse Source

Thumbnail

isundil 2 years ago
parent
commit
cdfb7d1cbf
10 changed files with 109 additions and 10 deletions
  1. 3 0
      main.js
  2. 2 2
      model/mediaService.js
  3. 1 0
      package.json
  4. 21 0
      router/api.js
  5. 10 0
      src/fileTypeManager.js
  6. 34 0
      src/filetype/imagemagick.js
  7. 1 0
      src/filetype/meta.js
  8. 25 8
      src/library.js
  9. 11 0
      src/libraryManager.js
  10. 1 0
      static/public/js/common.js

+ 3 - 0
main.js

@@ -10,6 +10,8 @@ const RouterUtils = require('./src/routerUtils.js').RouterUtils;
 
 const CR2Parser = require('./src/filetype/cr2.js').ExifParser;
 
+const UPDATE_INTERVAL = 1800000; // 30 min
+
 function App() {
     this.router = new Router({ static_route: __dirname+"/static/" });
     this.routerUtils = new RouterUtils(this);
@@ -32,6 +34,7 @@ App.prototype.init = async function() {
 App.prototype.run = async function() {
     http.createServer(this.router).listen(CONFIG.port);
     this.libraryManager.updateLibraries(this.databaseHelper);
+    setInterval(() => { this.libraryManager.updateLibraries(this.databaseHelper); }, UPDATE_INTERVAL);
 }
 
 console.info = () => {};

+ 2 - 2
model/mediaService.js

@@ -33,7 +33,7 @@ function reduceReqToMediaStruct(acc, i) {
     return acc;
 }
 
-module.exports.fetchOne = async function(app, md5sum) {
+module.exports.fetchOne = async function(app, md5sum, accessList) {
     let result = ((await app.databaseHelper.runSql(`
         select mediaFile.path, mediaFile.md5sum, mediaFile.date,
         mediaMeta.key as metaKey, mediaMeta.value as metaValue,
@@ -42,7 +42,7 @@ module.exports.fetchOne = async function(app, md5sum) {
         left join mediaMeta on mediaMeta.md5sum=mediaFile.md5sum
         left join mediaTag on mediaTag.md5sum=mediaFile.md5sum
         where mediaFile.md5sum=?`, md5sum)) || []).reduce(reduceReqToMediaStruct, {})[md5sum] || null;
-    return result;
+    return result.HaveAccess(accessList) ? result : null;
 }
 
 module.exports.fetchMedias = async function(app, startTs, count) {

+ 1 - 0
package.json

@@ -24,6 +24,7 @@
     "mime-types": "^2.1.35",
     "node-simple-router": "^0.10.2",
     "sqlite3": "^5.1.6",
+    "tmp": "^0.2.1",
     "whiskers": "^0.4.0"
   }
 }

+ 21 - 0
router/api.js

@@ -1,4 +1,5 @@
 
+const fs = require('fs');
 const Security = require('../src/security.js');
 const MediaService = require('../model/mediaService.js');
 
@@ -26,5 +27,25 @@ module.exports = { register: app => {
             data: await MediaService.fetchMediasWithAccess(app, 0, 50, req.accessList)
         });
     });
+    app.router.get("/api/media/thumbnail/:md5sum.png", async (req, res) => {
+        app.routerUtils.onApiRequest(req, res);
+        let data = await MediaService.fetchOne(app, req.params.md5sum, req.accessList);
+        if (!data)
+            return app.routerUtils.onPageNotFound(res);
+        try {
+            let thumbnail = await (await app.libraryManager.findMedia(data.path))?.createThumbnail(req.body?.w || 0, req.body?.h || 0);
+            if (!thumbnail)
+                return app.routerUtils.onPageNotFound(res);
+            res.setHeader("Content-Type", "image/png");
+            res.setHeader("Content-Length", fs.statSync(thumbnail.name)?.size || undefined);
+            let rd = fs.createReadStream(thumbnail.name);
+            rd.once('end', () => thumbnail.removeCallback());
+            rd.pipe(res);
+        }
+        catch (err) {
+            console.error(err);
+            app.routerUtils.onPageNotFound(res);
+        }
+    });
 }};
 

+ 10 - 0
src/fileTypeManager.js

@@ -4,6 +4,16 @@ const parsers = [
     require ('./filetype/meta.js')
 ];
 
+module.exports.createThumbnail = async function(fileObj, w, h) {
+    for (let i of parsers)
+    {
+        let result = await i.createThumbnail(fileObj, w, h);
+        if (result)
+            return result;
+    }
+    return null;
+}
+
 module.exports.createMeta = async function(fileObj) {
     let result = {};
     for (let i of await Promise.allSettled(parsers.map(i => i.parse(fileObj))))

+ 34 - 0
src/filetype/imagemagick.js

@@ -1,4 +1,6 @@
 
+const fs = require('fs');
+const tmp = require('tmp');
 const im = require('imagemagick');
 const geokit = require('geokit');
 
@@ -115,3 +117,35 @@ module.exports.parse = async (fileObj) => {
     return result;
 }
 
+module.exports.createThumbnail = (fileObj, width, height) => {
+    return new Promise((ok, ko) => {
+        if (!fileObj?.meta?.width || !fileObj?.meta?.height)
+            return null;
+        if (!width && !height)
+            width = height = 420;
+        let ratio = Math.min((width / fileObj.meta.width) || 1, (height / fileObj.meta.height) || 1);
+        const output = tmp.fileSync({ discardDescriptor: true });
+        if (ratio >= 1) {
+            im.convert([
+                fileObj.path,
+                `PNG:${output.name}`
+            ], err => {
+                if (err)
+                    throw err;
+                ok(output);
+            });
+            return;
+        }
+        im.resize({
+            srcPath: fileObj.path,
+            dstPath: `PNG:${output.name}`,
+            height: fileObj.meta.height * ratio,
+            width: fileObj.meta.width * ratio
+        }, err => {
+            if (err)
+                throw err;
+            ok(output);
+        });
+    });
+};
+

+ 1 - 0
src/filetype/meta.js

@@ -7,3 +7,4 @@ module.exports.parse = async (fileObj) => {
     };
 }
 
+module.exports.createThumbnail = async (fileObj, w, h) => { return null; };

+ 25 - 8
src/library.js

@@ -37,7 +37,7 @@ File.prototype.saveDb = async function(db, libraryHash) {
     let _this = this;
     entity.path = this.path;
     entity.md5sum = this.checksum;
-    entity.date = this.meta.dateTime || new Date(fs.statsSync(this.path)?.birthtimeMs || Date.now());
+    entity.date = this.meta.dateTime || new Date(fs.statSync(this.path)?.birthtimeMs || Date.now());
     await db.insertOne(entity);
     this.meta.photochamberImport = new Date();
     this.meta.libraryPath = libraryHash;
@@ -45,6 +45,10 @@ File.prototype.saveDb = async function(db, libraryHash) {
     await db.insertMultipleSameTable(metaEntities);
 }
 
+File.prototype.createThumbnail = async function(w, h) {
+    return await FileTypeManager.createThumbnail(this, w, h);
+}
+
 async function enrichAll(lib) {
     for (let i =0; i < lib.buff.length; i += 5)
         await Promise.allSettled(lib.buff.slice(i, i+5).map(i => i.enrich()));
@@ -57,7 +61,7 @@ async function Library_doUpdate(dbHelper, lib) {
     lib.buff = lib.buff.filter(i => dbItems.indexOf(i.path) === -1);
     await enrichAll(lib);
     lib.buff = lib.buff.filter(i => !!i.checksum);
-    await Promise.allSettled(lib.buff.map(i => i.saveDb(dbHelper, lib.dbHash)));
+    (await Promise.allSettled(lib.buff.map(i => i.saveDb(dbHelper, lib.dbHash)))).forEach(x => { if (x.status === 'rejected') { console.log(`Cannot update item: `, x.reason); }});
     lib.foundMedias = lib.foundMedias.concat(lib.buff);
     lib.buff = [];
 }
@@ -79,20 +83,33 @@ function Library_isValid(_this) {
     return fs.existsSync(_this.path);
 }
 
-function Library(path) {
-    this.path = path;
-    this.dbHash = md5String(path);
+function Library(p) {
+    this.path = path.normalize(p);
+    this.dbHash = md5String(this.path);
 }
 
 Library.prototype.updateLibrary = async function(dbHelper) {
     console.log(`Starting update of library ${this.path}`);
     this.foundMedias = [];
     this.buff = [];
-    if (Library_isValid(this))
-        await Library_depthupdate(dbHelper, this, this.path);
-    await Library_doUpdate(dbHelper, this)
+    this.errors = {};
+    try {
+        if (Library_isValid(this))
+            await Library_depthupdate(dbHelper, this, this.path);
+        await Library_doUpdate(dbHelper, this)
+    } catch (err) {
+        console.err(`Cannot update Library ${this.path}:`, err);
+    }
     console.log(`Done updating library ${this.path}. Managed ${this.foundMedias.length} new media files.`);
 }
 
+Library.prototype.findMedia = async function(p) {
+    if (!p.startsWith(this.path))
+        return null;
+    let result = new File(p, path.basename(p));
+    await result.enrich();
+    return result;
+}
+
 module.exports.Library = Library;
 

+ 11 - 0
src/libraryManager.js

@@ -1,6 +1,7 @@
 
 const CONFIG = require('./config.js');
 const Library = require('./library.js').Library;
+const Path = require('path');
 
 function LibraryManager() {
     this.libraries = [];
@@ -26,5 +27,15 @@ LibraryManager.prototype.updateLibraries = async function(dbHelper) {
 LibraryManager.prototype.isUpdating = function()
 { return this.updating; }
 
+LibraryManager.prototype.findMedia = async function(path) {
+    path = Path.normalize(path);
+    for (let i of this.libraries) {
+        let media = await i.findMedia(path);
+        if (media)
+            return media;
+    }
+    return null;
+}
+
 module.exports.LibraryManager = new LibraryManager();
 

+ 1 - 0
static/public/js/common.js

@@ -11,6 +11,7 @@ function ReadMediaList() {
     LoadingTasks.push(() => {
         $.get("/api/media/list", data => {
             MediaStorage.Instance.pushAll(data.data.map(i => new Media(i)));
+            $.get("/api/media/thumbnail/" + MediaStorage.Instance.newest.md5sum +".png");
             console.log(MediaStorage.Instance);
         });
     });