├── .eslintrc.js ├── .gitignore ├── bin └── build ├── config └── sample.yml ├── docs ├── gallery-image.jpg ├── gallery-list.jpg └── gallery-video.jpg ├── license.md ├── package.json ├── readme.md ├── src ├── cli.mjs ├── defaultConfig.mjs ├── js │ └── main.js ├── sass │ └── main.scss └── templates │ ├── conf.ejs │ └── xslt.ejs └── tests ├── assets ├── favicon-white.png └── htpasswd ├── example ├── CosmosDiscovery-Brno-2018-08-29_18-43-19.jpg ├── Hawaii-2017-12-21_14-44-29-Oahu.jpg ├── Hawaii-2017-12-23_11-15-42-Kauai.jpg ├── Hawaii-2017-12-23_11-34-07-Kauai.JPG ├── Hawaii-2017-12-23_11-41-59-Kauai.JPEG ├── Hawaii-2017-12-28_11-47-32-BigIsland-KilaueaCaldera-Panorama360.jpg ├── Hawaii-2017-12-29_18-38-03-BigIsland.jpg ├── Hawaii-2017-12-30_18-15-35-BigIsland-MaunaKea-Panorama360.jpg ├── Lipno-2020-08-21.mp4 ├── Lipno-2020-08-21.srt ├── Seychelles-20191223.mp4 ├── Seychelles-20191223.vtt └── subdirectory │ ├── Barcelona-2019-11-04_07-A.mp4 │ ├── Barcelona-2019-11-04_07-A.srt │ ├── Barcelona-2019-11-04_07-B.mp4 │ ├── Barcelona-2019-11-04_07-B.vtt │ ├── Mauritius-2018-12-23_10-39-18.jpg │ ├── Mauritius-2018-12-23_10-40-15.jpg │ ├── Reunion-2018-12-24_18-33-34.jpg │ └── Reunion-2018-12-28_14-50-43-Pano360.jpg ├── run-tests ├── ssl ├── gallery.test.crt ├── gallery.test.key └── gallery.test.pem ├── test-all-path.yml ├── test-all.yml ├── test-favicon.yml ├── test-http-port.yml ├── test-http.yml ├── test-httpauth.yml ├── test-https-httpport.yml ├── test-https-letsencrypt.yml ├── test-https-noredirect.yml ├── test-https-port.yml ├── test-light-theme.yml ├── test-simple.yml ├── test-subtitles.yml ├── test-thumbnails-cache.yml ├── test-thumbnails.yml ├── test-title.yml └── test-video-thumbnails.yml /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | node: true, 6 | }, 7 | extends: 'airbnb-base', 8 | parserOptions: { 9 | ecmaVersion: 2022, 10 | }, 11 | rules: { 12 | 'indent': [2, 'tab', { SwitchCase: 1 }], // we use tabs 13 | 'no-console': 'off', 14 | 'no-plusplus': ['error', { allowForLoopAfterthoughts: true }], // i++ is allowed for "for" 15 | 'no-tabs': 0, // we use tabs instead of spaces 16 | 'prefer-template': 0, // we use ${} when appropriate 17 | }, 18 | settings: { 19 | 'import/resolver': { 20 | node: { 21 | extensions: ['.js'], 22 | } 23 | }, 24 | 'import/extensions': [ 25 | '.js', 26 | ], 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /bin/build: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | node ${0%/*}/../src/cli.mjs $@ 4 | -------------------------------------------------------------------------------- /config/sample.yml: -------------------------------------------------------------------------------- 1 | # = required = 2 | nginxConfDir: /etc/nginx/sites-available/ 3 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery/ 4 | serverName: gallery.test 5 | galleryDir: /var/www/gallery 6 | 7 | # = optional = 8 | path: null # /path/ 9 | port: null # null = use default - 80 for HTTP, 443 for HTTPS 10 | thumbnails: false # bool - yes/no or string with cache directory 11 | videoThumbnails: false # or string with cache directory 12 | lightTheme: false # bool - yes/no 13 | title: 'Gallery' #

and 14 | favicon: false # or string with a path to custom favicon (in PNG format) 15 | subtitles: 16 | convertSrtToVtt: false 17 | ssl: false 18 | #certificate: '' # required if ssl is set 19 | #certificateKey: '' # required if ssl is set 20 | #redirectHttpToHttps: true # bool - yes/no 21 | #httpPort: 80 # HTTP port to redirect to HTTPS or for Let's encrypt (if Let's encrypt is used, must be 80) 22 | #letsEncrypt: false # or string with a path to directory to store ACME challenge 23 | httpAuth: false # or string with htpasswd auth file 24 | -------------------------------------------------------------------------------- /docs/gallery-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/docs/gallery-image.jpg -------------------------------------------------------------------------------- /docs/gallery-list.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/docs/gallery-list.jpg -------------------------------------------------------------------------------- /docs/gallery-video.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/docs/gallery-video.jpg -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | New BSD License 2 | --------------- 3 | 4 | Copyright 2023 Jakub Trmota (https://forrest79.dev) 5 | 6 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 11 | 12 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@babel/plugin-transform-runtime": "^7.23", 4 | "@babel/preset-env": "^7.23", 5 | "@rollup/plugin-babel": "^6.0", 6 | "@rollup/plugin-commonjs": "^25.0", 7 | "@rollup/plugin-node-resolve": "^15.2", 8 | "@rollup/plugin-replace": "^5.0", 9 | "@rollup/plugin-terser": "^0.4", 10 | "commander": "^11.0", 11 | "core-js": "^3.33", 12 | "ejs": "^3.1", 13 | "eslint": "^8.51", 14 | "eslint-config-airbnb-base": "^15.0", 15 | "exifr": "^7.1", 16 | "glightbox": "^3.2", 17 | "node-sass": "^9.0", 18 | "photo-sphere-viewer": "^4.8", 19 | "rollup": "^4.0", 20 | "shareon": "^2.3", 21 | "tippy.js": "^6.3", 22 | "yaml": "^2.3" 23 | }, 24 | "scripts": { 25 | "build": "node ./src/cli.mjs", 26 | "eslint": "npx eslint ./src --ext js,mjs" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # StaticNginxGallery 2 | 3 | Static gallery (images, videos with subtitles, other files) on the top of the nginx web server. 4 | 5 | **Based on https://stackoverflow.com/questions/39575873/is-there-a-way-to-create-a-simple-static-image-gallery-in-nginx-without-any-thir.** 6 | 7 | Just put your files in the folder and get it instantly in the gallery - no background service is running, files are read directly from your file system. With the XSTL is directory listing converted to an HTML page with some controlling JavaScript and CSS. 8 | 9 | ![List files](/docs/gallery-list.jpg) 10 | 11 | ![Image detail](/docs/gallery-image.jpg) 12 | 13 | ![Video detail](/docs/gallery-video.jpg) 14 | 15 | ## About gallery 16 | 17 | ### Features: 18 | 19 | - images... 20 | - reading EXIF (via [exifr](https://github.com/MikeKovarik/exifr)) 21 | - show GPS on the Google Maps 22 | - view photo sphere panoramas (via [Photo Sphere Viewer](https://photo-sphere-viewer.js.org/)) 23 | - an optional automatic image thumbnails with an optional cache 24 | - videos (via [Plyr](https://plyr.io/)) 25 | - an optional thumbnail generation (`ffmpeg` installed on system needed) 26 | - with optional auto conversion SRT subtitles to supported VTT 27 | - other files (just view and download) 28 | - sharing link to the gallery 29 | - sharing on social networks (via [shareon](https://shareon.js.org/)) 30 | - as lightbox is used [GLightbox](https://biati-digital.github.io/glightbox/) 31 | - HTTP or HTTPS (Let's encrypt support - via external tools - [dehyhrated](https://dehydrated.io/) for example) 32 | - custom gallery title/favicon and many more settings 33 | - password access (via HTTP Basic Auth) 34 | 35 | ### What do I need? 36 | 37 | [nginx](https://www.nginx.com/) to run gallery and [node.js](https://nodejs.org) version 16 or greater with `npm` to generate configuration. If you want to generate also video thumbnails, `ffmpeg` must be installed on your system. Nothing more, really. This tool generates nginx configuration and one XSLT, JS and CSS file to control whole gallery and all supported tools. 38 | 39 | You need nginx with the [XSTL module](http://nginx.org/en/docs/http/ngx_http_xslt_module.html). In Debian/Ubuntu world this is the `nginx-full` package. 40 | 41 | If you want an automatic image thumbnail (with a cache) you need also the [images filter module](http://nginx.org/en/docs/http/ngx_http_image_filter_module.html). Still the `nginx-full` in Debian/Ubuntu. 42 | 43 | For an automatic SRT to VTT conversion you need the [lua module](https://github.com/openresty/lua-nginx-module). This is the `nginx-extras` package. 44 | 45 | And for video thumbnails generation you also need the `lua module` and [ffmpeg](https://ffmpeg.org/) installed on your system and binaries must be set in `PATH` (you can run `ffmpeg` command from everywhere). Thumbnail generation was tested on `ffmpeg` version `4.3.0`. 46 | 47 | > This configuration generator was tested on the linux only. 48 | 49 | ## How to use it 50 | 51 | Clone this repository or download source files from GitHub and install npm modules with `npm install`. 52 | 53 | Then you need configuration file in YAML: 54 | 55 | ```yaml 56 | nginxConfDir: /etc/nginx/sites-available/ 57 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery/ 58 | serverName: gallery.test 59 | galleryDir: /var/www/gallery 60 | ``` 61 | 62 | These are the only 4 required items you always must specify: 63 | - `nginxConfDir` - where nginx configuration file will be generated (file will have `static-nginx-gallery` name) - don't forget to enable configuration in nginx, when it's not generated into included files - in Debian `sudo ln -s /etc/nginx/sites-available/static-nginx-gallery /etc/nginx/sites-enabled/static-nginx-gallery` and `sudo service nginx reload` 64 | - `nginxAppDir` - where the other (`XSLT`, `JS` and `CSS`) files will be stored 65 | - `serverName` - domain where the gallery will be accessible 66 | - `galleryDir` - root directory with all your files (you can access also all subdirectories) 67 | 68 | There are some more optional options: 69 | 70 | ```yaml 71 | path: null # /path/ 72 | port: null # null = use default - 80 for HTTP, 443 for HTTPS 73 | thumbnails: false # bool - yes/no or string with cache directory 74 | videoThumbnails: false # or string with cache directory 75 | lightTheme: false # bool - yes/no 76 | title: 'Gallery' # <h1> and <title> 77 | favicon: false # or string with a path to custom favicon (in PNG format) 78 | subtitles: 79 | convertSrtToVtt: false 80 | ssl: false # explain later 81 | httpAuth: false # or string with htpasswd auth file 82 | ``` 83 | 84 | - `path` - by default, gallery is accessible at serverName host `/`, this can change the default path to `path` (for example `http://gallery.test/some-path/` it you set `serverName: gallery.test` and `path: 'some-path'`) 85 | - `port` - `null` is used for default value - `80` for HTTP or `443` for HTTPS version, but you can use your own, if you need 86 | - `thumbnails` - by default, thumbnail images in the list are original images, this could cause to download a lot of data for one page. When you set this to `true`, nginx will generate for all images smaller thumbnails on the fly, so data usage will be less, but more processor time will be used to generate thumbnails on every request. The last option is to provide a `string` with a directory, where this thumbnails will be cached, so every thumbnail will be generated just once. This has just one disadvantage: when a thumbnail is generated, it's never regenerated, even if the original image is changed. You must remove cached thumbnail to regenerate it. *`images` module is needed in nginx for this* 87 | - `videoThumbnails` - by default, videos has no thumbnail images. When you set directory for cache, nginx will generate via `ffmpeg` thumbnails and these thumbnails will be cached, so every thumbnail will be generated just once. This has the same disadvantage as image thumbnails: when a thumbnail is generated, it's never regenerated, even if the original video is changed. You must remove cached thumbnail to regenerate it. *`lua` module is needed in nginx for this and `ffmpeg` must be installed on the system* 88 | - `lightTheme` - gallery is by default in dark theme, set this to `true` and light theme will be used 89 | - `title` - can change default `<title>` and `<h1>` which is `Gallery` 90 | - `favicon` - can change default favicon file, use path to a PNG image 91 | - `subtitels` -> `convertSrtToVtt` - if set to `true` try to automatically convert existing SRT subtitles to VTT (only VTT subtitles are supported in video player) - *`LUA` module is needed in nginx* 92 | - `httpAuth` - use can specify a `string` with a path to htpasswd file for enabling user/password access to the gallery 93 | 94 | > How to generate `htpasswd` file? In linux with installed `openssl` you can use `printf "USER:$(openssl passwd -crypt PASSWORD)\n" >> .htpasswd` - just change `USER` with the correct username and `PASSWORD` with the correct password. `.htpasswd` is path to the generated file. 95 | 96 | In most cases, you want to run your gallery on HTTPS protocol. You can also use own HTTPS (or other, for example for access restriction I can recommend [oauth2-proxy](https://github.com/oauth2-proxy/oauth2-proxy)) proxy and keep running gallery on HTTP: 97 | 98 | ```yaml 99 | ssl: 100 | certificate: '/etc/letsencrypt/certs/gallery.test/fullchain.pem' # required if ssl is set 101 | certificateKey: '/etc/letsencrypt/certs/gallery.test/privkey.pem' # required if ssl is set 102 | redirectHttpToHttps: true # bool - yes/no 103 | httpPort: 80 # HTTP port to redirect to HTTPS or for Let's encrypt (if Let's encrypt is used, must be 80) 104 | letsEncrypt: false # or string with a path to directory to store ACME challenge 105 | ``` 106 | 107 | - `certificate` - path to SSL certificate, required 108 | - `certificateKey` - path to SSL certificate key, required 109 | - `redirectHttpToHttps` - by default, for HTTPS is also generated a HTTP block, that just redirect HTTP to HTTPS. If you don't need this, set config to `false` 110 | - `httpPort` - belongs to the previous config, set port for HTTP block for redirect, default is `80`. If you use Let's encrypt config, you must keep it to `80`. 111 | - `letsEncrypt` - when `string` path is provided, to HTTP block is generated snippet to enable auto generating Let's encrypt certificates. You also need some tool to generate certificates, for example [dehyhrated](https://dehydrated.io/). Before first certificate is generated, you must comment SSL certificate and SSL certificate key lines. 112 | 113 | ### Build configuration 114 | 115 | So, we have `config.yml` with our configuration, now just run: 116 | 117 | ```bash 118 | bin/build config.yml 119 | ``` 120 | 121 | or: 122 | 123 | ```bash 124 | npm run build config.yml 125 | ``` 126 | 127 | You will probably need `root` rights to proper save configuration, so use: 128 | 129 | ```bash 130 | sudo bin/build config.yml 131 | ``` 132 | 133 | And after that don't forget to reload your nginx configuration. In Debian/Ubuntu like this: 134 | 135 | ```bash 136 | sudo service nginx reload 137 | ``` 138 | 139 | You can also add `-d` or `--debug` option to disable JS and CSS minimization. 140 | 141 | ### Preferable nginx configuration 142 | 143 | It's recommended to add this option to main nginx configuration, the `http` section: 144 | 145 | ``` 146 | variables_hash_max_size 4096 147 | ``` 148 | 149 | In Debian/Ubuntu in file `/etc/nginx/nginx.conf`. 150 | 151 | ## Development 152 | 153 | You can make your own changes in CSS, JavaScript and HTML structure. 154 | 155 | CSS is in the [src/sass/main.scss](/src/sass/main.scss) file and is compiled with the [SASS preprocessor](https://sass-lang.com/). 156 | 157 | JavaScript is in the [src/js/main.js](/src/js/main.js) file and is compiled with the [Rollup](https://rollupjs.org/). 158 | 159 | HTML can be changed in the [XSLT template](/src/templates/xslt.ejs). 160 | 161 | And the generated nginx configurations can be updated in the [configuration](/src/templates/conf.ejs). 162 | 163 | XSLT and nginx configurations are generated with the [EJS templates](https://ejs.co/). 164 | 165 | To check your JS coding standard, run [eslint](https://eslint.org/). 166 | 167 | ```bash 168 | npm run eslint 169 | ``` 170 | 171 | ### Tests 172 | 173 | In this repository, there are some tests configurations. They expected Debian/Ubuntu linux with installed `nginx-extras` package, symlinked directory [tests](/tests) to the `/var/www/static-nginx-gallery/` and pointed host `gallery.test` to the nginx. Self-signed certificate is used for SSL configuration. 174 | 175 | You can run `sudo tests/run-tests` that will try all test configurations one by one. 176 | -------------------------------------------------------------------------------- /src/cli.mjs: -------------------------------------------------------------------------------- 1 | import { Command } from 'commander/esm.mjs'; 2 | import fs from 'fs'; 3 | import crypto from 'crypto'; 4 | import yaml from 'yaml'; 5 | import ejs from 'ejs'; 6 | import * as rollup from 'rollup'; 7 | import rollupNodeResolve from '@rollup/plugin-node-resolve'; 8 | import rollupReplace from '@rollup/plugin-replace'; 9 | import rollupCommonJS from '@rollup/plugin-commonjs'; 10 | import { babel } from '@rollup/plugin-babel'; 11 | import terser from '@rollup/plugin-terser'; 12 | import sass from 'node-sass'; 13 | import { fileURLToPath } from 'url'; 14 | import * as path from 'path'; 15 | import defaultConfig from './defaultConfig.mjs'; 16 | 17 | // Helpers 18 | 19 | const srcDir = path.dirname(fileURLToPath(import.meta.url)); 20 | const sha1 = (data) => crypto.createHash('sha1').update(data).digest('hex'); 21 | 22 | // Prepare build options 23 | 24 | const program = new Command(); 25 | 26 | program.option('-d, --debug', 'build debug version (don\'t minimize CSS and JS)'); 27 | program.parse(process.argv); 28 | 29 | if (program.args.length === 0) { 30 | console.error('You must specify YAML file with the build configuration.'); 31 | process.exit(1); 32 | } else if (program.args.length > 1) { 33 | console.error('You must specify only one YAML file with the build configuration.'); 34 | process.exit(1); 35 | } 36 | 37 | const configYamlFile = program.args[0]; 38 | const debugMode = program.opts().debug === true; 39 | 40 | // Prepare configuration 41 | 42 | const config = defaultConfig; 43 | let configYaml = null; 44 | 45 | try { 46 | configYaml = yaml.parse(fs.readFileSync(configYamlFile, 'utf8')); 47 | 48 | if (configYaml.nginxConfDir !== undefined) { 49 | config.nginxConfDir = configYaml.nginxConfDir.replace(/(\/)$/, ''); 50 | } 51 | 52 | if (configYaml.nginxAppDir !== undefined) { 53 | config.nginxAppDir = configYaml.nginxAppDir.replace(/(\/)$/, ''); 54 | } 55 | 56 | if (configYaml.serverName !== undefined) { 57 | config.serverName = configYaml.serverName; 58 | } 59 | 60 | if (configYaml.galleryDir !== undefined) { 61 | config.galleryDir = configYaml.galleryDir; 62 | } 63 | 64 | if (configYaml.path !== undefined) { 65 | config.path = configYaml.path; 66 | } 67 | 68 | if (configYaml.port !== undefined) { 69 | config.port = configYaml.port; 70 | } 71 | 72 | if (configYaml.thumbnails !== undefined) { 73 | config.thumbnails = configYaml.thumbnails; 74 | } 75 | 76 | if (configYaml.videoThumbnails !== undefined) { 77 | config.videoThumbnails = configYaml.videoThumbnails; 78 | } 79 | 80 | if (configYaml.lightTheme !== undefined) { 81 | config.lightTheme = configYaml.lightTheme; 82 | } 83 | 84 | if (configYaml.title !== undefined) { 85 | config.title = configYaml.title; 86 | } 87 | 88 | if (configYaml.favicon !== undefined) { 89 | config.favicon = configYaml.favicon; 90 | } 91 | 92 | if (typeof configYaml.subtitles === 'object') { 93 | if (configYaml.subtitles.convertSrtToVtt !== undefined) { 94 | config.subtitlesConvertSrtToVtt = configYaml.subtitles.convertSrtToVtt; 95 | } 96 | } 97 | 98 | if (typeof configYaml.ssl === 'object') { 99 | config.ssl = true; 100 | 101 | if (configYaml.ssl.certificate !== undefined) { 102 | config.sslCertificate = configYaml.ssl.certificate; 103 | } 104 | 105 | if (configYaml.ssl.certificateKey !== undefined) { 106 | config.sslCertificateKey = configYaml.ssl.certificateKey; 107 | } 108 | 109 | if (configYaml.ssl.redirectHttpToHttps !== undefined) { 110 | config.sslRedirectHttpToHttps = configYaml.ssl.redirectHttpToHttps; 111 | } 112 | 113 | if (configYaml.ssl.httpPort !== undefined) { 114 | config.sslHttpPort = configYaml.ssl.httpPort; 115 | } 116 | 117 | if (configYaml.ssl.letsEncrypt !== undefined) { 118 | config.sslLetsEncrypt = configYaml.ssl.letsEncrypt; 119 | } 120 | } 121 | 122 | if (configYaml.httpAuth !== undefined) { 123 | config.httpAuth = configYaml.httpAuth; 124 | } 125 | 126 | let configError = false; 127 | 128 | if (config.nginxConfDir === null) { 129 | console.error('You must specify "nginxConfDir" in your configuration.'); 130 | configError = true; 131 | } 132 | 133 | if (config.nginxAppDir === null) { 134 | console.error('You must specify "nginxAppDir" in your configuration.'); 135 | configError = true; 136 | } 137 | 138 | if (config.serverName === null) { 139 | console.error('You must specify "serverName" in your configuration.'); 140 | configError = true; 141 | } 142 | 143 | if (config.galleryDir === null) { 144 | console.error('You must specify "galleryDir" in your configuration.'); 145 | configError = true; 146 | } 147 | 148 | if (config.ssl) { 149 | if (config.sslCertificate === null) { 150 | console.error('You must specify "ssl.certificate" in your configuration.'); 151 | configError = true; 152 | } 153 | 154 | if (config.sslCertificateKey === null) { 155 | console.error('You must specify "ssl.certificateKey" in your configuration.'); 156 | configError = true; 157 | } 158 | 159 | if (config.sslLetsEncrypt && (config.sslHttpPort !== 80)) { 160 | console.error('When using "ssl.letsEncrypt" in your configuration, then "ssl.httpPort" must be 80.'); 161 | configError = true; 162 | } 163 | } 164 | 165 | if (configError) { 166 | process.exit(2); 167 | } 168 | 169 | if (config.port === null) { 170 | config.port = config.ssl === false ? 80 : 443; 171 | } 172 | 173 | if (config.path !== null) { 174 | config.path = '/' + config.path.replace(/^(\/)/, ''); 175 | } else { 176 | config.path = ''; 177 | } 178 | } catch (e) { 179 | console.error(`Can't read YAML configuration file "${configYamlFile}".`, e); 180 | process.exit(1); 181 | } 182 | 183 | // Compile CSS 184 | const renderedCss = sass.renderSync({ 185 | file: path.join(srcDir, 'sass/main.scss'), 186 | outputStyle: debugMode ? 'expanded' : 'compressed', 187 | }).css.toString().trim(); 188 | 189 | // Compile JS 190 | 191 | const bundle = await rollup.rollup({ 192 | input: path.join(srcDir, 'js/main.js'), 193 | plugins: [ 194 | rollupReplace({ 195 | 'process.env.NODE_ENV': JSON.stringify(debugMode ? 'development' : 'production'), 196 | preventAssignment: true, 197 | }), 198 | rollupNodeResolve(), 199 | rollupCommonJS(), 200 | babel({ 201 | babelHelpers: 'runtime', 202 | presets: [ 203 | [ 204 | '@babel/preset-env', 205 | { 206 | bugfixes: true, 207 | corejs: '3.9', 208 | targets: '>0.25%', 209 | useBuiltIns: 'usage', 210 | }, 211 | ], 212 | ], 213 | compact: false, 214 | plugins: ['@babel/plugin-transform-runtime'], 215 | exclude: 'node_modules/core-js/**', 216 | }), 217 | ].concat(debugMode ? [] : [terser()]), 218 | }); 219 | const { output } = await bundle.generate({ 220 | format: 'iife', 221 | name: 'gallery', 222 | }); 223 | const renderedJs = output[0].code.trim(); 224 | 225 | // Build application 226 | 227 | config.cssSha1 = sha1(renderedCss); 228 | config.jsSha1 = sha1(renderedJs); 229 | 230 | const nginxTemplate = fs.readFileSync(path.join(srcDir, 'templates/conf.ejs'), 'utf8'); 231 | const renderedNginxTemplate = ejs.render(nginxTemplate, config); 232 | 233 | const xsltTemplate = fs.readFileSync(path.join(srcDir, 'templates/xslt.ejs'), 'utf8'); 234 | const renderedXsltTemplate = ejs.render(xsltTemplate, config); 235 | 236 | fs.writeFileSync(config.nginxConfDir + '/static-nginx-gallery', renderedNginxTemplate); 237 | fs.writeFileSync(config.nginxAppDir + '/static-nginx-gallery.xslt', renderedXsltTemplate); 238 | fs.writeFileSync(config.nginxAppDir + '/static-nginx-gallery.css', renderedCss); 239 | fs.writeFileSync(config.nginxAppDir + '/static-nginx-gallery.js', renderedJs); 240 | -------------------------------------------------------------------------------- /src/defaultConfig.mjs: -------------------------------------------------------------------------------- 1 | const defaultConfig = { 2 | // required 3 | nginxConfDir: null, 4 | nginxAppDir: null, 5 | serverName: null, 6 | galleryDir: null, 7 | // optional 8 | path: null, 9 | port: null, 10 | thumbnails: false, 11 | videoThumbnails: false, 12 | lightTheme: false, 13 | title: 'Gallery', 14 | favicon: false, 15 | subtitlesConvertSrtToVtt: false, 16 | ssl: false, 17 | sslCertificate: null, 18 | sslCertificateKey: null, 19 | sslRedirectHttpToHttps: true, 20 | sslHttpPort: 80, 21 | sslLetsEncrypt: false, 22 | httpAuth: false, 23 | // can't be rewritten in user configuration 24 | thumbnailsResizeWidth: 800, 25 | thumbnailsResizeHeight: 800, 26 | imageFilterBuffer: '100M', 27 | imageFilterJpegQuality: 85, 28 | imageFilterWebpQuality: 80, 29 | imageFilterSharpen: 50, 30 | }; 31 | 32 | export default defaultConfig; 33 | -------------------------------------------------------------------------------- /src/js/main.js: -------------------------------------------------------------------------------- 1 | import GLightbox from 'glightbox'; 2 | import { Viewer } from 'photo-sphere-viewer'; 3 | import tippy from 'tippy.js'; 4 | import { init as shareon } from '../../node_modules/shareon/dist/shareon.es'; 5 | import exifr from '../../node_modules/exifr/dist/lite.esm'; 6 | 7 | const exifSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> 8 | <path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/> 9 | <path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"/> 10 | </svg>`; 11 | 12 | const mapSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> 13 | <path d="M12.166 8.94c-.524 1.062-1.234 2.12-1.96 3.07A31.493 31.493 0 0 1 8 14.58a31.481 31.481 0 0 1-2.206-2.57c-.726-.95-1.436-2.008-1.96-3.07C3.304 7.867 3 6.862 3 6a5 5 0 0 1 10 0c0 .862-.305 1.867-.834 2.94zM8 16s6-5.686 6-10A6 6 0 0 0 2 6c0 4.314 6 10 6 10z"/> 14 | <path d="M8 8a2 2 0 1 1 0-4 2 2 0 0 1 0 4zm0 1a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/> 15 | </svg>`; 16 | 17 | const photo360Svg = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> 18 | <path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855A7.97 7.97 0 0 0 5.145 4H7.5V1.077zM4.09 4a9.267 9.267 0 0 1 .64-1.539 6.7 6.7 0 0 1 .597-.933A7.025 7.025 0 0 0 2.255 4H4.09zm-.582 3.5c.03-.877.138-1.718.312-2.5H1.674a6.958 6.958 0 0 0-.656 2.5h2.49zM4.847 5a12.5 12.5 0 0 0-.338 2.5H7.5V5H4.847zM8.5 5v2.5h2.99a12.495 12.495 0 0 0-.337-2.5H8.5zM4.51 8.5a12.5 12.5 0 0 0 .337 2.5H7.5V8.5H4.51zm3.99 0V11h2.653c.187-.765.306-1.608.338-2.5H8.5zM5.145 12c.138.386.295.744.468 1.068.552 1.035 1.218 1.65 1.887 1.855V12H5.145zm.182 2.472a6.696 6.696 0 0 1-.597-.933A9.268 9.268 0 0 1 4.09 12H2.255a7.024 7.024 0 0 0 3.072 2.472zM3.82 11a13.652 13.652 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5H3.82zm6.853 3.472A7.024 7.024 0 0 0 13.745 12H11.91a9.27 9.27 0 0 1-.64 1.539 6.688 6.688 0 0 1-.597.933zM8.5 12v2.923c.67-.204 1.335-.82 1.887-1.855.173-.324.33-.682.468-1.068H8.5zm3.68-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.65 13.65 0 0 1-.312 2.5zm2.802-3.5a6.959 6.959 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5h2.49zM11.27 2.461c.247.464.462.98.64 1.539h1.835a7.024 7.024 0 0 0-3.072-2.472c.218.284.418.598.597.933zM10.855 4a7.966 7.966 0 0 0-.468-1.068C9.835 1.897 9.17 1.282 8.5 1.077V4h2.355z"/> 19 | </svg>`; 20 | 21 | const shareSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> 22 | <path d="M13.5 1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM11 2.5a2.5 2.5 0 1 1 .603 1.628l-6.718 3.12a2.499 2.499 0 0 1 0 1.504l6.718 3.12a2.5 2.5 0 1 1-.488.876l-6.718-3.12a2.5 2.5 0 1 1 0-3.256l6.718-3.12A2.5 2.5 0 0 1 11 2.5zm-8.5 4a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zm11 5.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z"/> 23 | </svg>`; 24 | 25 | const openSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> 26 | <path fill-rule="evenodd" d="M14 2.5a.5.5 0 0 0-.5-.5h-6a.5.5 0 0 0 0 1h4.793L2.146 13.146a.5.5 0 0 0 .708.708L13 3.707V8.5a.5.5 0 0 0 1 0v-6z"/> 27 | </svg>`; 28 | 29 | const downloadSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> 30 | <path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5z"/> 31 | <path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708l3 3z"/> 32 | </svg>`; 33 | 34 | const copySvg = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> 35 | <path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/> 36 | <path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/> 37 | </svg>`; 38 | 39 | const copySvgDone = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> 40 | <path fill-rule="evenodd" d="M10.854 7.146a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708 0l-1.5-1.5a.5.5 0 1 1 .708-.708L7.5 9.793l2.646-2.647a.5.5 0 0 1 .708 0z"/> 41 | <path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/> 42 | <path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/> 43 | </svg>`; 44 | 45 | const closeSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> 46 | <path fill-rule="evenodd" d="M13.854 2.146a.5.5 0 0 1 0 .708l-11 11a.5.5 0 0 1-.708-.708l11-11a.5.5 0 0 1 .708 0Z"/> 47 | <path fill-rule="evenodd" d="M2.146 2.146a.5.5 0 0 0 0 .708l11 11a.5.5 0 0 0 .708-.708l-11-11a.5.5 0 0 0-.708 0Z"/> 48 | </svg>`; 49 | 50 | const customLightboxHTML = ` 51 | <div id="glightbox-body" class="glightbox-container"> 52 | <div class="gloader visible"></div> 53 | <div class="goverlay"></div> 54 | <div class="gcontainer"> 55 | <div id="glightbox-slider" class="gslider"></div> 56 | <button class="gnext gbtn" tabindex="0" aria-label="Next">{nextSVG}</button> 57 | <button class="gprev gbtn" tabindex="1" aria-label="Previous">{prevSVG}</button> 58 | <div class="sng-buttons"> 59 | <a id="sng-exif" class="sng-button hidden" tabindex="9" aria-label="Show exif info">${exifSvg}</a> 60 | <a id="sng-map" class="sng-button hidden" tabindex="8" aria-label="Show on map">${mapSvg}</a> 61 | <a id="sng-photo360" class="sng-button hidden" tabindex="7" aria-label="Show photoshere">${photo360Svg}</a> 62 | <a id="sng-share" class="sng-button" tabindex="6" data-position="4" aria-label="Share">${shareSvg}</a> 63 | <a id="sng-open" class="sng-button" tabindex="5" data-position="3" aria-label="Open original in new tab" target="_blank">${openSvg}</a> 64 | <a id="sng-download" class="sng-button" tabindex="4" data-position="2" aria-label="Download" download>${downloadSvg}</a> 65 | <a id="sng-copy" class="sng-button" tabindex="3" data-position="1" aria-label="Copy URL to clipboard" download>${copySvg}</a> 66 | </div> 67 | <button class="gclose gbtn" tabindex="2" aria-label="Close">${closeSvg}</button> 68 | </div> 69 | 70 | <div id="sng-exif-box-container"> 71 | <div id="sng-exif-box"></div> 72 | </div> 73 | 74 | <div id="sng-map-box-container"> 75 | <div id="sng-map-box"></div> 76 | </div> 77 | 78 | <div id="sng-share-box-container"> 79 | <div class="shareon" id="sng-share-box"> 80 | <a class="facebook"></a> 81 | <a class="linkedin"></a> 82 | <a class="pinterest"></a> 83 | <a class="pocket"></a> 84 | <a class="reddit"></a> 85 | <a class="telegram"></a> 86 | <a class="twitter"></a> 87 | <a class="whatsapp"></a> 88 | </div> 89 | </div> 90 | </div> 91 | 92 | <div id="sng-photo360-viewer-box" class="hidden"></div> 93 | `; 94 | 95 | const lightbox = GLightbox({ 96 | lightboxHTML: customLightboxHTML, 97 | touchNavigation: true, 98 | loop: true, 99 | autoplayVideos: false, 100 | plyr: { 101 | config: { 102 | captions: { 103 | active: true, 104 | language: 'Subtitles', 105 | update: true, 106 | }, 107 | }, 108 | }, 109 | }); 110 | 111 | let buttons = null; 112 | 113 | let copyButton = null; 114 | let copyTimeout = null; 115 | 116 | let exifButton = null; 117 | let mapButton = null; 118 | let photo360Button = null; 119 | let photo360Viewer = null; 120 | 121 | let exifTippy = null; 122 | let exifTippyVisible = false; 123 | 124 | let mapTippy = null; 125 | let mapTippyVisible = false; 126 | 127 | let shareTippy = null; 128 | let shareTippyVisible = false; 129 | 130 | const updateButtons = () => { 131 | let position = 1; 132 | for (let i = buttons.length - 1; i >= 0; i--) { 133 | const button = buttons[i]; 134 | 135 | if (button.classList.contains('hidden')) { 136 | button.removeAttribute('data-position'); 137 | } else { 138 | button.setAttribute('data-position', position.toString()); 139 | position += 1; 140 | } 141 | } 142 | }; 143 | 144 | const destroy = () => { 145 | copyButton.innerHTML = copySvg; 146 | if (copyTimeout !== null) { 147 | clearTimeout(copyTimeout); 148 | copyTimeout = null; 149 | } 150 | 151 | if (exifTippy !== null) { 152 | exifTippy.destroy(); 153 | exifTippy = null; 154 | } 155 | 156 | if (mapTippy !== null) { 157 | mapTippy.destroy(); 158 | mapTippy = null; 159 | } 160 | 161 | if (shareTippy !== null) { 162 | shareTippy.destroy(); 163 | shareTippy = null; 164 | } 165 | }; 166 | 167 | const updateExif = (info, gps) => { 168 | let html = ''; 169 | 170 | if (Object.keys(info).length !== 0) { 171 | html += '<table>'; 172 | Object.entries(info).forEach(([key, value]) => { 173 | html += `<tr><th>${key}</th><td>${value}</td></tr>`; 174 | }); 175 | html += '</table>'; 176 | } 177 | 178 | if (Object.keys(gps).length !== 0) { 179 | html += '<strong>GPS</strong><table>'; 180 | Object.entries(gps).forEach(([key, value]) => { 181 | html += `<tr><th>${key}</th><td>${value}</td></tr>`; 182 | }); 183 | html += '</table>'; 184 | } 185 | 186 | document.getElementById('sng-exif-box').innerHTML = html; 187 | }; 188 | 189 | const updateMap = (latitude, longitude) => { 190 | document.getElementById('sng-map-box').innerHTML = `<iframe width="100%" height="100%" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" src="https://maps.google.com/maps?width=100%25&height=100%25&hl=en&q=${latitude},${longitude}&t=&z=10&ie=UTF8&iwloc=B&output=embed">`; 191 | }; 192 | 193 | const addSubtitles = (url) => { 194 | const track = document.createElement('track'); 195 | track.kind = 'captions'; 196 | track.label = 'Subtitles'; 197 | track.srclang = 'Subtitles'; 198 | track.default = true; 199 | track.src = url; 200 | document.querySelector('.gslide.loaded.current video').appendChild(track); 201 | }; 202 | 203 | lightbox.on('open', () => { 204 | buttons = document.querySelectorAll('div.sng-buttons .sng-button'); 205 | 206 | copyButton = document.getElementById('sng-copy'); 207 | copyButton.addEventListener('click', (event) => { 208 | event.stopPropagation(); 209 | 210 | navigator.clipboard.writeText(window.location.toString()) 211 | .then(() => { 212 | copyButton.innerHTML = copySvgDone; 213 | setTimeout(() => { 214 | copyButton.innerHTML = copySvg; 215 | }, 2000); 216 | }) 217 | .catch((err) => { 218 | tippy(copyButton, { 219 | trigger: 'manual', 220 | appendTo: document.body, 221 | zIndex: 999999, 222 | content: err, 223 | }).show(); 224 | }); 225 | }); 226 | 227 | exifButton = document.getElementById('sng-exif'); 228 | exifButton.addEventListener('click', (event) => { 229 | event.stopPropagation(); 230 | 231 | if (exifTippy === null) { 232 | exifTippy = tippy(exifButton, { 233 | trigger: 'manual', 234 | interactive: true, 235 | appendTo: document.body, 236 | zIndex: 999999, 237 | content: document.getElementById('sng-exif-box-container').innerHTML, 238 | allowHTML: true, 239 | onShown() { 240 | exifTippyVisible = true; 241 | }, 242 | onHidden() { 243 | exifTippyVisible = false; 244 | }, 245 | }); 246 | } 247 | 248 | if (exifTippyVisible) { 249 | exifTippy.hide(); 250 | } else { 251 | exifTippy.show(); 252 | } 253 | }); 254 | 255 | mapButton = document.getElementById('sng-map'); 256 | mapButton.addEventListener('click', (event) => { 257 | event.stopPropagation(); 258 | 259 | if (mapTippy === null) { 260 | mapTippy = tippy(mapButton, { 261 | trigger: 'manual', 262 | interactive: true, 263 | appendTo: document.body, 264 | zIndex: 999999, 265 | content: document.getElementById('sng-map-box-container').innerHTML, 266 | allowHTML: true, 267 | onShown() { 268 | mapTippyVisible = true; 269 | }, 270 | onHidden() { 271 | mapTippyVisible = false; 272 | }, 273 | }); 274 | } 275 | 276 | if (mapTippyVisible) { 277 | mapTippy.hide(); 278 | } else { 279 | mapTippy.show(); 280 | } 281 | }); 282 | 283 | const photo360ViewerBox = document.getElementById('sng-photo360-viewer-box'); 284 | photo360Button = document.getElementById('sng-photo360'); 285 | photo360Button.addEventListener('click', (event) => { 286 | event.stopPropagation(); 287 | 288 | photo360ViewerBox.classList.remove('hidden'); 289 | if (photo360Viewer === null) { 290 | photo360Viewer = new Viewer({ 291 | container: photo360ViewerBox, 292 | panorama: photo360Button.getAttribute('data-url'), 293 | caption: photo360Button.getAttribute('data-title'), 294 | touchmoveTwoFingers: true, 295 | mousewheelCtrlKey: true, 296 | navbar: [ 297 | 'autorotate', 298 | 'zoom', 299 | 'move', 300 | 'fullscreen', 301 | 'caption', 302 | { 303 | content: 'Close', 304 | onClick: () => { 305 | photo360ViewerBox.classList.add('hidden'); 306 | }, 307 | }, 308 | ], 309 | }); 310 | } else { 311 | photo360Viewer.setPanorama(photo360Button.getAttribute('data-url')).then(() => { 312 | photo360ViewerBox.querySelector('.psv-caption-content').innerHTML = photo360Button.getAttribute('data-title'); 313 | }); 314 | } 315 | }); 316 | 317 | const shareButton = document.getElementById('sng-share'); 318 | shareButton.addEventListener('click', (event) => { 319 | event.stopPropagation(); 320 | 321 | if (shareTippy === null) { 322 | shareon(); 323 | shareTippy = tippy(shareButton, { 324 | trigger: 'manual', 325 | interactive: true, 326 | appendTo: document.body, 327 | zIndex: 999999, 328 | maxWidth: 186, 329 | content: document.getElementById('sng-share-box-container').innerHTML, 330 | allowHTML: true, 331 | onShown() { 332 | shareTippyVisible = true; 333 | }, 334 | onHidden() { 335 | shareTippyVisible = false; 336 | }, 337 | }); 338 | } 339 | 340 | if (shareTippyVisible) { 341 | shareTippy.hide(); 342 | } else { 343 | shareTippy.show(); 344 | } 345 | }); 346 | }); 347 | 348 | lightbox.on('close', () => { 349 | destroy(); 350 | 351 | const uri = window.location.toString(); 352 | if (uri.indexOf('#') > 0) { 353 | window.history.replaceState(null, null, uri.substring(0, uri.indexOf('#'))); 354 | } 355 | }); 356 | 357 | lightbox.on('slide_changed', ({ current }) => { 358 | updateButtons(); 359 | 360 | destroy(); 361 | 362 | // Prev and current are objects that contain the following data 363 | const { slideConfig } = current; 364 | 365 | const url = slideConfig.href; 366 | const fileName = slideConfig.title; // in title is the file name, in href is complete URL 367 | 368 | window.history.replaceState(null, null, '#file-' + btoa(fileName)); 369 | 370 | // Exif + Map + Photo360 371 | exifButton.classList.add('hidden'); 372 | mapButton.classList.add('hidden'); 373 | photo360Button.classList.add('hidden'); 374 | 375 | if (slideConfig.type === 'image') { 376 | exifr.parse(url, { 377 | // Segments (JPEG APP Segment, PNG Chunks, HEIC Boxes, etc...) 378 | tiff: true, 379 | xmp: true, 380 | // Sub-blocks inside TIFF segment 381 | ifd0: true, // aka image 382 | ifd1: false, // aka thumbnail 383 | exif: true, 384 | gps: true, 385 | interop: false, 386 | // Formatters 387 | translateKeys: true, 388 | translateValues: true, 389 | reviveValues: true, 390 | sanitize: true, 391 | mergeOutput: false, 392 | silentErrors: true, 393 | }).then((data) => { 394 | const info = {}; 395 | const gps = {}; 396 | 397 | if (data.exif) { 398 | Object.entries(data.exif).forEach(([key, value]) => { 399 | info[key] = value.toString(); 400 | }); 401 | } 402 | 403 | if (data.ifd0) { 404 | Object.entries(data.ifd0).forEach(([key, value]) => { 405 | info[key] = value.toString(); 406 | }); 407 | } 408 | 409 | if (data.gps) { 410 | const { latitude, longitude } = data.gps; 411 | updateMap(latitude, longitude); 412 | mapButton.classList.remove('hidden'); 413 | 414 | Object.entries(data.gps).forEach(([key, value]) => { 415 | gps[key] = value.toString(); 416 | }); 417 | } 418 | 419 | if (info !== {} || gps !== {}) { 420 | updateExif(info, gps); 421 | exifButton.classList.remove('hidden'); 422 | } 423 | 424 | if (data.GPano) { 425 | if (data.GPano.IsPhotosphere || data.GPano.UsePanoramaViewer) { 426 | photo360Button.setAttribute('data-url', url); 427 | photo360Button.setAttribute('data-title', fileName); 428 | photo360Button.classList.remove('hidden'); 429 | } 430 | } 431 | 432 | updateButtons(); 433 | }); 434 | } else if (slideConfig.type === 'video') { 435 | if (!slideConfig.subtitlesChecked) { 436 | slideConfig.subtitlesChecked = true; 437 | 438 | const subtitlesUrl = url.split('.').slice(0, -1).join('.') + '.vtt'; 439 | 440 | fetch(subtitlesUrl).then((response) => { 441 | if (response.status === 200) { 442 | addSubtitles(subtitlesUrl); 443 | slideConfig.subtitlesUrl = subtitlesUrl; 444 | } 445 | }); 446 | } else if (slideConfig.subtitlesUrl) { 447 | addSubtitles(slideConfig.subtitlesUrl); 448 | } 449 | } 450 | 451 | // Activate sharing 452 | const shareBox = document.getElementById('sng-share-box'); 453 | shareBox.setAttribute('data-url', url); 454 | shareBox.setAttribute('data-title', fileName); 455 | 456 | // Activate open in new tab and download 457 | document.getElementById('sng-download').setAttribute('href', url); 458 | document.getElementById('sng-open').setAttribute('href', url); 459 | }); 460 | 461 | // open file by hash 462 | if (window.location.hash.startsWith('#file-')) { 463 | try { 464 | const filename = atob(window.location.hash.substr(6)); 465 | const items = document.querySelectorAll('a.glightbox'); 466 | const itemCount = items.length; 467 | for (let i = 0; i < itemCount; i++) { 468 | if (items[i].getAttribute('href') === filename) { 469 | lightbox.openAt(i); 470 | break; 471 | } 472 | } 473 | } catch (e) { 474 | console.error(e); 475 | } 476 | } 477 | -------------------------------------------------------------------------------- /src/sass/main.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: sans-serif; 3 | font-size: 16px; 4 | } 5 | 6 | * { 7 | box-sizing: border-box; 8 | } 9 | 10 | body { 11 | background-color: #121212; 12 | color: #f5f5f5; 13 | } 14 | 15 | h1 { 16 | background-color: #424242; 17 | padding: 0.8em; 18 | border-radius: 0.5em; 19 | 20 | span { 21 | font-weight: normal; 22 | color: #e0e0e0; 23 | text-transform: uppercase; 24 | } 25 | } 26 | 27 | .content { 28 | width: 100%; 29 | margin: auto; 30 | 31 | @media (min-width: 40em) { 32 | width: 40em; 33 | } 34 | 35 | @media (min-width: 80em) { 36 | width: 80em; 37 | } 38 | } 39 | 40 | .directories { 41 | width: 100%; 42 | margin-bottom: 1em; 43 | 44 | a { 45 | display: inline-block; 46 | margin: 0.3em; 47 | padding: 0.7em; 48 | border: 1px solid #f5f5f5; 49 | background-color: #616161; 50 | color: #f5f5f5; 51 | border-radius: 0.5em; 52 | font-size: 0.7em; 53 | text-transform: uppercase; 54 | text-decoration: none; 55 | &:hover { 56 | background-color: #bdbdbd; 57 | color: #000; 58 | text-decoration: underline; 59 | 60 | svg { 61 | color: #000; 62 | } 63 | } 64 | 65 | svg { 66 | color: #f5f5f5; 67 | margin-right: 0.5em; 68 | vertical-align: sub; 69 | &.up { 70 | margin: 0; 71 | } 72 | } 73 | } 74 | } 75 | 76 | .gallery { 77 | display: flex; 78 | flex-flow: row wrap; 79 | align-content: flex-start; 80 | align-items: stretch; 81 | width: 100%; 82 | margin: auto; 83 | 84 | .galleryItem { 85 | flex: 1 1 auto; 86 | margin: 0.3em; 87 | border: 1px solid #f5f5f5; 88 | position: relative; 89 | width: 12em; 90 | height: 13em; 91 | overflow: hidden; 92 | 93 | a { 94 | display: block; 95 | width: 100%; 96 | height: 100%; 97 | text-align: center; 98 | &[data-type="video"] { 99 | background-repeat: no-repeat; 100 | background-size: cover; 101 | } 102 | } 103 | 104 | img { 105 | width: 100%; 106 | height: 100%; 107 | object-fit: cover; 108 | transition: 0.2s; 109 | } 110 | 111 | svg { 112 | color: #f5f5f5; 113 | width: 7em; 114 | height: 7em; 115 | margin-top: 2em; 116 | transition: 0.2s; 117 | } 118 | 119 | figcaption { 120 | font-size: 0.7em; 121 | background-color: rgba(#000, 0.4); 122 | color: #fff; 123 | position: absolute; 124 | bottom: 0; 125 | left: 0; 126 | right: 0; 127 | padding: 1em; 128 | } 129 | 130 | @media (min-width: 40em) { 131 | width: 10em; 132 | height: 10em; 133 | 134 | svg { 135 | width: 6em; 136 | height: 6em; 137 | margin-top: 2em; 138 | } 139 | } 140 | 141 | @media (min-width: 80em) { 142 | width: 17em; 143 | height: 17em; 144 | 145 | svg { 146 | width: 9em; 147 | height: 9em; 148 | margin-top: 4em; 149 | } 150 | } 151 | 152 | &:hover img { 153 | transform: scale(1.2); 154 | } 155 | 156 | &:hover svg { 157 | transform: scale(1.1); 158 | } 159 | } 160 | } 161 | 162 | .hidden { 163 | display: none; 164 | } 165 | 166 | body.light { 167 | background-color: #f5f5f5; 168 | color: #121212; 169 | 170 | h1 { 171 | background-color: #e0e0e0; 172 | 173 | span { 174 | color: #616161; 175 | } 176 | } 177 | 178 | .directories { 179 | a { 180 | border: 1px solid #121212; 181 | background-color: #9e9e9e; 182 | color: #121212; 183 | &:hover { 184 | background-color: #424242; 185 | color: #f5f5f5; 186 | 187 | svg { 188 | color: #f5f5f5 189 | } 190 | } 191 | 192 | svg { 193 | color: #121212; 194 | } 195 | } 196 | } 197 | 198 | .gallery { 199 | .galleryItem { 200 | border-color: #121212; 201 | 202 | svg { 203 | color: #121212; 204 | } 205 | } 206 | } 207 | } 208 | 209 | // GLightBox 210 | 211 | @import "../../node_modules/glightbox/dist/css/glightbox"; 212 | 213 | .sng-button { 214 | @extend .gclose; 215 | 216 | &.hidden { 217 | display: none; 218 | } 219 | } 220 | 221 | .glightbox-clean { 222 | .gcontainer { 223 | .sng-buttons { 224 | .sng-button { 225 | &[data-position="1"] { 226 | right: 50px; 227 | } 228 | 229 | &[data-position="2"] { 230 | right: 90px; 231 | } 232 | 233 | &[data-position="3"] { 234 | right: 130px; 235 | } 236 | 237 | &[data-position="4"] { 238 | right: 170px; 239 | } 240 | 241 | &[data-position="5"] { 242 | right: 210px; 243 | } 244 | 245 | &[data-position="6"] { 246 | right: 250px; 247 | } 248 | 249 | &[data-position="7"] { 250 | right: 290px; 251 | } 252 | } 253 | } 254 | } 255 | } 256 | 257 | @media (min-width: 992px) { 258 | .glightbox-clean { 259 | .gcontainer { 260 | .sng-buttons { 261 | .sng-button { 262 | &[data-position="1"] { 263 | right: 60px; 264 | } 265 | 266 | &[data-position="2"] { 267 | right: 100px; 268 | } 269 | 270 | &[data-position="3"] { 271 | right: 140px; 272 | } 273 | 274 | &[data-position="4"] { 275 | right: 180px; 276 | } 277 | 278 | &[data-position="5"] { 279 | right: 220px; 280 | } 281 | 282 | &[data-position="6"] { 283 | right: 260px; 284 | } 285 | 286 | &[data-position="7"] { 287 | right: 300px; 288 | } 289 | } 290 | } 291 | } 292 | } 293 | } 294 | 295 | // Tippy 296 | 297 | @import "../../node_modules/tippy.js/dist/tippy"; 298 | 299 | // Shareon 300 | 301 | @import "../../node_modules/shareon/dist/shareon.min"; 302 | 303 | // Exif 304 | 305 | #sng-exif-box { 306 | max-height: 300px; 307 | overflow-y: scroll; 308 | 309 | strong { 310 | display: block; 311 | font-size: 14px; 312 | margin-top: 15px; 313 | padding: 2px; 314 | } 315 | 316 | table { 317 | font-size: 11px; 318 | th { 319 | text-align: left; 320 | vertical-align: top; 321 | margin: 0; 322 | padding: 0; 323 | } 324 | td { 325 | vertical-align: top; 326 | margin: 0; 327 | padding: 0; 328 | } 329 | } 330 | } 331 | 332 | // Photo360 333 | 334 | @import "../../node_modules/photo-sphere-viewer/dist/photo-sphere-viewer"; 335 | 336 | #sng-photo360-viewer-box { 337 | position: fixed; 338 | top: 0; 339 | left: 0; 340 | bottom: 0; 341 | right: 0; 342 | z-index: 9999999; 343 | } 344 | -------------------------------------------------------------------------------- /src/templates/conf.ejs: -------------------------------------------------------------------------------- 1 | <% if (ssl && (sslRedirectHttpToHttps || sslLetsEncrypt)) { %> 2 | 3 | # http to https 4 | 5 | server { 6 | server_name <%= serverName %>; 7 | 8 | listen <%= sslHttpPort %>; 9 | 10 | <% if (sslLetsEncrypt) { %> 11 | location ^~ /.well-known/acme-challenge/ { 12 | alias <%= sslLetsEncrypt %>; 13 | default_type text/plain; 14 | access_log off; 15 | log_not_found off; 16 | try_files $uri =404; 17 | } 18 | 19 | <% } %> 20 | <% if (sslRedirectHttpToHttps) { %> 21 | 22 | location <%= path %>/ { 23 | return 301 https://$host<% if ((sslHttpPort !== 80) || (port !== 443)) { %>:<%= port %><% } %>$request_uri; 24 | } 25 | <% } %> 26 | } 27 | 28 | 29 | <% } %> 30 | server { 31 | server_name <%= serverName %>; 32 | <% if (ssl) { %> 33 | 34 | ############################################ 35 | # ssl 36 | 37 | listen <%= port %> ssl http2; 38 | 39 | ssl_certificate <%= sslCertificate %>; 40 | ssl_certificate_key <%= sslCertificateKey %>; 41 | 42 | ssl_session_timeout 1d; 43 | ssl_session_cache shared:SSL:10m; 44 | ssl_session_tickets off; 45 | 46 | ssl_protocols TLSv1.2 TLSv1.3; 47 | ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; 48 | ssl_prefer_server_ciphers off; 49 | 50 | add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; 51 | 52 | ssl_stapling on; 53 | ssl_stapling_verify on; 54 | <% } else { %> 55 | listen <%= port %>; 56 | <% } %> 57 | <% if (httpAuth !== false) { %> 58 | 59 | ############################################ 60 | # http auth 61 | 62 | auth_basic "<%= title %> login"; 63 | auth_basic_user_file <%= httpAuth %>; 64 | <% } %> 65 | 66 | ############################################ 67 | # basic 68 | 69 | charset utf-8; 70 | 71 | location /favicon.ico { 72 | log_not_found off; 73 | } 74 | 75 | location /robots.txt { 76 | log_not_found off; 77 | } 78 | 79 | ############################################ 80 | # remove trailing dot from host 81 | 82 | if ($http_host ~ \.$) { 83 | return 301 $scheme://$host$request_uri; 84 | } 85 | 86 | ############################################ 87 | # gzip 88 | 89 | gzip on; 90 | gzip_vary on; 91 | gzip_min_length 1024; 92 | gzip_comp_level 5; 93 | gzip_proxied expired no-cache no-store private auth; 94 | gzip_types text/html text/plain text/css text/javascript application/javascript application/x-javascript; 95 | 96 | ############################################ 97 | # gallery 98 | 99 | location <%= path %>/ { 100 | alias <%= galleryDir %>/; 101 | 102 | autoindex on; 103 | autoindex_format xml; 104 | xslt_string_param uri $request_uri; 105 | xslt_stylesheet <%= nginxAppDir %>/static-nginx-gallery.xslt; 106 | try_files $uri $uri/ =404; 107 | } 108 | 109 | ############################################ 110 | # CSS and JS files 111 | 112 | location = <%= path %>/@<%= cssSha1 %>.css { 113 | expires 365d; 114 | default_type text/css; 115 | alias <%= nginxAppDir %>/static-nginx-gallery.css; 116 | } 117 | 118 | location = <%= path %>/@<%= jsSha1 %>.js { 119 | expires 365d; 120 | default_type 'text/javascript'; 121 | alias <%= nginxAppDir %>/static-nginx-gallery.js; 122 | } 123 | <% if (typeof favicon === 'string') { %> 124 | 125 | ############################################ 126 | # custom favicon 127 | 128 | location = <%= path %>/favicon.png { 129 | alias <%= favicon %>; 130 | } 131 | 132 | <% } %> 133 | <% if (typeof thumbnails === 'string') { %> 134 | 135 | ############################################ 136 | # thumbnails with cache 137 | 138 | location <%= path %>/cache/thumbnails { 139 | alias <%= thumbnails %>/thumbnails; 140 | 141 | expires 1d; 142 | add_header Pragma public; 143 | add_header Cache-Control 'public'; 144 | 145 | try_files $uri @imageProxy; 146 | } 147 | 148 | location @imageProxy { 149 | internal; 150 | 151 | if (!-f <%= galleryDir %>/${image_original_uri}) { 152 | return 404; 153 | } 154 | 155 | proxy_pass http://127.0.0.1:8080/image_resize/${image_original_uri}?w=<%= thumbnailsResizeWidth %>&h=<%= thumbnailsResizeHeight %>; 156 | proxy_store <%= thumbnails %>/thumbnails/${image_original_uri}; 157 | proxy_store_access user:rw group:rw all:rw; 158 | proxy_temp_path <%= nginxAppDir %>/temp; 159 | proxy_set_header Host $host; 160 | } 161 | <% } else if (thumbnails) { %> 162 | 163 | ############################################ 164 | # thumbnails 165 | 166 | location <%= path %>/thumbnails { 167 | alias <%= galleryDir %>; 168 | 169 | expires 1d; 170 | add_header Pragma public; 171 | add_header Cache-Control 'public'; 172 | 173 | image_filter resize <%= thumbnailsResizeWidth %> <%= thumbnailsResizeHeight %>; 174 | image_filter_buffer <%= imageFilterBuffer %>; 175 | image_filter_jpeg_quality <%= imageFilterJpegQuality %>; 176 | image_filter_webp_quality <%= imageFilterWebpQuality %>; 177 | image_filter_transparency on; 178 | image_filter_sharpen <%= imageFilterSharpen %>; 179 | } 180 | <% } %> 181 | <% if (typeof videoThumbnails === 'string') { %> 182 | 183 | ############################################ 184 | # cached video thumbnails 185 | 186 | location <%= path %>/cache/video-thumbnails { 187 | alias <%= videoThumbnails %>/video-thumbnails; 188 | 189 | expires 1d; 190 | add_header Pragma public; 191 | add_header Cache-Control 'public'; 192 | 193 | try_files $uri @videoProxy; 194 | } 195 | 196 | location @videoProxy { 197 | internal; 198 | 199 | if (!-f <%= galleryDir %>/${video_original_uri}) { 200 | return 404; 201 | } 202 | 203 | proxy_pass http://127.0.0.1:8080/video_thumbnail/${video_original_uri}?w=<%= thumbnailsResizeWidth %>&h=<%= thumbnailsResizeHeight %>; 204 | proxy_store <%= videoThumbnails %>/video-thumbnails/${video_original_uri}.jpg; 205 | proxy_store_access user:rw group:rw all:rw; 206 | proxy_temp_path <%= nginxAppDir %>/temp; 207 | proxy_set_header Host $host; 208 | } 209 | <% } %> 210 | <% if (subtitlesConvertSrtToVtt) { %> 211 | 212 | ############################################ 213 | # convert SRT subtitles to VTT 214 | 215 | location ~* ^<%= path %>/.+\.vtt$ { 216 | alias <%= galleryDir %>; 217 | try_files $uri<% if (path !== '') { %> ${subtitles_base_uri}.vtt<% } %> @convertSubtitles; 218 | } 219 | 220 | location @convertSubtitles { 221 | internal; 222 | 223 | # if source srt subtitles doesn't exists... 224 | if (!-f <%= galleryDir %>/${subtitles_base_uri}.srt) { 225 | return 404; 226 | } 227 | 228 | add_header Access-Control-Allow-Origin <%= serverName %>; 229 | add_header Access-Control-Allow-Methods GET; 230 | 231 | content_by_lua_block { 232 | -- load original srt subtitles 233 | local subtitlesFile = assert(io.open('<%= galleryDir %>' .. ngx.var.subtitles_base_uri .. '.srt', 'rb')) 234 | if not subtitlesFile then 235 | -- here we're returning HTTP 400, this should never happen, so we want to know about this anomaly :-) 236 | return ngx.exit(ngx.HTTP_BAD_REQUEST) 237 | end 238 | local content = subtitlesFile:read('*all') 239 | subtitlesFile:close() 240 | 241 | -- check for UTF-8 BOM header and remove it 242 | if (content:byte(1) == 239) and (content:byte(2) == 187) and (content:byte(3) == 191) then 243 | content = content:sub(4) 244 | end 245 | 246 | -- add VTT header and simply replace ',' with '.' in time ranges 247 | ngx.say('WEBVTT\n\n' .. content:gsub(':([%d]+),([%d]+)', ':%1.%2')); 248 | } 249 | } 250 | <% } %> 251 | } 252 | <% if ((typeof thumbnails === 'string') || (typeof videoThumbnails === 'string')) { %> 253 | 254 | server { 255 | listen 127.0.0.1:8080; 256 | <% if (typeof thumbnails === 'string') { %> 257 | 258 | location /image_resize { 259 | alias <%= galleryDir %>; 260 | 261 | image_filter resize $arg_w $arg_h; 262 | image_filter_buffer <%= imageFilterBuffer %>; 263 | image_filter_jpeg_quality <%= imageFilterJpegQuality %>; 264 | image_filter_webp_quality <%= imageFilterWebpQuality %>; 265 | image_filter_transparency on; 266 | image_filter_sharpen <%= imageFilterSharpen %>; 267 | } 268 | <% } %> 269 | <% if (typeof videoThumbnails === 'string') { %> 270 | 271 | location /video_thumbnail { 272 | content_by_lua_block { 273 | -- ffmpeg -i input.mp4 -vf "thumbnail,scale=w=320:h=240:force_original_aspect_ratio=increase" -frames:v 1 -f image2pipe - 274 | -- sub 17 is to strip location prefix from file name 275 | -- for ffmpeg debug remove '2>/dev/null' and check nginx error log 276 | local ffmpeg = io.popen('ffmpeg -i <%= galleryDir %>' .. string.sub(ngx.var.uri, 17) .. ' -vf "thumbnail,scale=w=' .. ngx.var.arg_w .. ':h=' .. ngx.var.arg_h .. ':force_original_aspect_ratio=increase" -frames:v 1 -f image2pipe - 2>/dev/null') 277 | local thumbnail = ffmpeg:read('*a') 278 | ffmpeg:close() 279 | 280 | if string.len(thumbnail) < 10 then -- probably thumbnail generation failed 281 | -- black 1x1 JPEG 282 | thumbnail = string.char(255,216,255,224,0,16,74,70,73,70,0,1,1,0,0,1,0,1,0,0) .. string.char(255,219,0,67,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1) .. string.char(1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1) .. string.char(1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,3) .. string.char(3,3,3,3,3,3,3,3,3,255,219,0,67,1,1,1,1,1,1,1) .. string.char(1,1,1,1,2,2,1,2,2,3,3,3,3,3,3,3,3,3,3,3) .. string.char(3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3) .. string.char(3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,255,192) .. string.char(0,17,8,0,1,0,1,3,1,17,0,2,17,1,3,17,1,255,196,0) .. string.char(20,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,11,255) .. string.char(196,0,20,16,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0) .. string.char(0,255,196,0,20,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0) .. string.char(0,0,0,255,196,0,20,17,1,0,0,0,0,0,0,0,0,0,0,0) .. string.char(0,0,0,0,0,255,218,0,12,3,1,0,2,17,3,17,0,63,0,63) .. string.char(240,127,255,217) 283 | end 284 | 285 | ngx.header.content_type = 'image/jpeg' 286 | ngx.say(thumbnail); 287 | } 288 | } 289 | <% } %> 290 | } 291 | <% if (typeof thumbnails === 'string') { %> 292 | 293 | map $uri $image_original_uri { 294 | ~^<%= path %>/cache/thumbnails/(?<image_uri>.+)$ $image_uri; 295 | } 296 | <% } %> 297 | <% if (typeof videoThumbnails === 'string') { %> 298 | 299 | map $uri $video_original_uri { 300 | ~^<%= path %>/cache/video-thumbnails/(?<video_uri>.+)\.jpg$ $video_uri; 301 | } 302 | <% } %> 303 | <% } %> 304 | <% if (subtitlesConvertSrtToVtt) { %> 305 | 306 | map $uri $subtitles_base_uri { 307 | ~^<%= path %>(?<base_uri>/.+)\.vtt$ $base_uri; 308 | } 309 | <% } %> 310 | -------------------------------------------------------------------------------- /src/templates/xslt.ejs: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 3 | <xsl:output method="html" encoding="utf-8" indent="yes" /> 4 | <xsl:variable name="processedUri" select="<% if (path === '') { %>$uri<% } else { %>substring-after($uri, '<%= path %>')<% } %>" /> 5 | <xsl:template match="/"> 6 | <xsl:text disable-output-escaping='yes'><!DOCTYPE html></xsl:text> 7 | <html> 8 | <head> 9 | <title><%= title %>: <xsl:value-of select="$processedUri" /> 10 | 11 | 12 | <% if (typeof favicon === 'string') { %> 13 | 14 | <% } else { %> 15 | 16 | <% } %> 17 | 18 | 19 | class="light"<% } %>> 20 |
21 |

<%= title %>

22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 | 42 | 73 | 74 |
75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /tests/assets/favicon-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/tests/assets/favicon-white.png -------------------------------------------------------------------------------- /tests/assets/htpasswd: -------------------------------------------------------------------------------- 1 | user:YCIjuBjpOd/z6 2 | -------------------------------------------------------------------------------- /tests/example/CosmosDiscovery-Brno-2018-08-29_18-43-19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/tests/example/CosmosDiscovery-Brno-2018-08-29_18-43-19.jpg -------------------------------------------------------------------------------- /tests/example/Hawaii-2017-12-21_14-44-29-Oahu.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/tests/example/Hawaii-2017-12-21_14-44-29-Oahu.jpg -------------------------------------------------------------------------------- /tests/example/Hawaii-2017-12-23_11-15-42-Kauai.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/tests/example/Hawaii-2017-12-23_11-15-42-Kauai.jpg -------------------------------------------------------------------------------- /tests/example/Hawaii-2017-12-23_11-34-07-Kauai.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/tests/example/Hawaii-2017-12-23_11-34-07-Kauai.JPG -------------------------------------------------------------------------------- /tests/example/Hawaii-2017-12-23_11-41-59-Kauai.JPEG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/tests/example/Hawaii-2017-12-23_11-41-59-Kauai.JPEG -------------------------------------------------------------------------------- /tests/example/Hawaii-2017-12-28_11-47-32-BigIsland-KilaueaCaldera-Panorama360.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/tests/example/Hawaii-2017-12-28_11-47-32-BigIsland-KilaueaCaldera-Panorama360.jpg -------------------------------------------------------------------------------- /tests/example/Hawaii-2017-12-29_18-38-03-BigIsland.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/tests/example/Hawaii-2017-12-29_18-38-03-BigIsland.jpg -------------------------------------------------------------------------------- /tests/example/Hawaii-2017-12-30_18-15-35-BigIsland-MaunaKea-Panorama360.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/tests/example/Hawaii-2017-12-30_18-15-35-BigIsland-MaunaKea-Panorama360.jpg -------------------------------------------------------------------------------- /tests/example/Lipno-2020-08-21.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/tests/example/Lipno-2020-08-21.mp4 -------------------------------------------------------------------------------- /tests/example/Lipno-2020-08-21.srt: -------------------------------------------------------------------------------- 1 | 1 2 | 00:00:01,000 --> 00:00:02,000 3 | Lipno-2020-08-21 - VTT from SRT subtitles 4 | -------------------------------------------------------------------------------- /tests/example/Seychelles-20191223.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/tests/example/Seychelles-20191223.mp4 -------------------------------------------------------------------------------- /tests/example/Seychelles-20191223.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 1 4 | 00:00:01.000 --> 00:00:02.000 5 | Seychelles-20191223 - VTT subtitles 6 | -------------------------------------------------------------------------------- /tests/example/subdirectory/Barcelona-2019-11-04_07-A.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/tests/example/subdirectory/Barcelona-2019-11-04_07-A.mp4 -------------------------------------------------------------------------------- /tests/example/subdirectory/Barcelona-2019-11-04_07-A.srt: -------------------------------------------------------------------------------- 1 | 1 2 | 00:00:01,000 --> 00:00:02,000 3 | Barcelona_2019-11-04_07-A - VTT from SRT subtitles 4 | -------------------------------------------------------------------------------- /tests/example/subdirectory/Barcelona-2019-11-04_07-B.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/tests/example/subdirectory/Barcelona-2019-11-04_07-B.mp4 -------------------------------------------------------------------------------- /tests/example/subdirectory/Barcelona-2019-11-04_07-B.vtt: -------------------------------------------------------------------------------- 1 | WEBVTT 2 | 3 | 1 4 | 00:00:01.000 --> 00:00:02.000 5 | Barcelona_2019-11-04_07-B - VTT subtitles 6 | -------------------------------------------------------------------------------- /tests/example/subdirectory/Mauritius-2018-12-23_10-39-18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/tests/example/subdirectory/Mauritius-2018-12-23_10-39-18.jpg -------------------------------------------------------------------------------- /tests/example/subdirectory/Mauritius-2018-12-23_10-40-15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/tests/example/subdirectory/Mauritius-2018-12-23_10-40-15.jpg -------------------------------------------------------------------------------- /tests/example/subdirectory/Reunion-2018-12-24_18-33-34.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/tests/example/subdirectory/Reunion-2018-12-24_18-33-34.jpg -------------------------------------------------------------------------------- /tests/example/subdirectory/Reunion-2018-12-28_14-50-43-Pano360.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forrest79/static-nginx-gallery/d9fae9b8786d9069d81513d95b3690c7839c0af6/tests/example/subdirectory/Reunion-2018-12-28_14-50-43-Pano360.jpg -------------------------------------------------------------------------------- /tests/run-tests: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ "$EUID" -ne 0 ]]; then 4 | echo "Please run as root or with sudo" 5 | exit 6 | fi 7 | 8 | GREEN='\033[0;32m' 9 | LIGHT_RED='\033[1;31m' 10 | YELLOW='\033[1;33m' 11 | CYAN='\033[0;36m' 12 | NC='\033[0m' # No Color 13 | 14 | prepare_test() { 15 | printf "${YELLOW}Preparing '%s' ... ${NC}" "$1"; 16 | 17 | "${0%/*}/../bin/build" "${0%/*}/$1" --debug && service nginx reload && printf "${YELLOW}DONE\n\n${LIGHT_RED}Actual config:\n--------------${NC}\n" && cat "${0%/*}/$1" && printf "\n\n"; 18 | 19 | printf "\n${CYAN}Press any key to prepare next test...${NC}\n\n"; 20 | 21 | read -n 1 -s -r; 22 | } 23 | 24 | # HTTPS version 25 | 26 | prepare_test "test-simple.yml"; 27 | 28 | prepare_test "test-light-theme.yml"; 29 | 30 | prepare_test "test-title.yml"; 31 | 32 | prepare_test "test-favicon.yml"; 33 | 34 | prepare_test "test-thumbnails.yml"; 35 | 36 | prepare_test "test-thumbnails-cache.yml"; 37 | 38 | prepare_test "test-video-thumbnails.yml"; 39 | 40 | prepare_test "test-subtitles.yml"; 41 | 42 | prepare_test "test-all.yml"; 43 | 44 | prepare_test "test-all-path.yml"; 45 | 46 | prepare_test "test-httpauth.yml"; 47 | 48 | prepare_test "test-https-port.yml"; 49 | 50 | prepare_test "test-https-noredirect.yml"; 51 | 52 | prepare_test "test-https-httpport.yml"; 53 | 54 | prepare_test "test-https-letsencrypt.yml"; 55 | 56 | # HTTP version 57 | 58 | prepare_test "test-http.yml"; 59 | 60 | prepare_test "test-http-port.yml"; 61 | 62 | printf "${GREEN}All tests were processed${NC}...\n"; 63 | -------------------------------------------------------------------------------- /tests/ssl/gallery.test.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDVjCCAj6gAwIBAgIUOyJRr54pqXviO701Fpo5CZwEDSYwDQYJKoZIhvcNAQEL 3 | BQAwQjELMAkGA1UEBhMCQ1oxDzANBgNVBAgMBlByYWd1ZTEPMA0GA1UEBwwGUHJh 4 | Z3VlMREwDwYDVQQKDAhERVZDRVJUUzAeFw0yMTA5MjAyMTA0MjhaFw0zMTA5MTgy 5 | MTA0MjhaMFsxCzAJBgNVBAYTAkNaMQ8wDQYDVQQIDAZQcmFndWUxDzANBgNVBAcM 6 | BlByYWd1ZTERMA8GA1UECgwIREVWQ0VSVFMxFzAVBgNVBAMMDiouZ2FsbGVyeS50 7 | ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApD4bJnE2jklhlp6A 8 | UVHrsORApNYk2rT1jjPBD0NAS4o00cNxaxpGEr1WBI2nhizLnRFuRwSx7XJN1/YL 9 | i+BJ6HlRhjK/rnTY4h3D5XLTiQTuOMFR0OU7vZhs4vWts1MmtaWmuAnu2nudVMlL 10 | U3IcFVapos8RDaTupD1CsxfiOurqN7DdGCNth8lobdgfZ2NdQt5XSCAVit64wFnV 11 | JZG9JduMRh3aAHoz9gQDqXLdnVJCbS4nDkaRfsRSdPIeaTK7FrglCWIrDSMLuNbv 12 | MwfJhbTafU0e2Un1vPyGLaYue/ZPsMl7rgf/yHqmgCegOEJNzPNYDJjJ3n/2Eh4T 13 | rhLhLQIDAQABoyswKTAnBgNVHREEIDAeggxnYWxsZXJ5LnRlc3SCDiouZ2FsbGVy 14 | eS50ZXN0MA0GCSqGSIb3DQEBCwUAA4IBAQBQTc/sUV9xnvkkui0RHFRch7CJ++ti 15 | 1lgCj8RTTaISwkmzNnidV8K1ic5yflQzK28xx391VgIsxJS2VyQ4gKXLLwZkBGBX 16 | W+yRZoNo4hq7g0Q0vxvzYbk6lFxmmCPU8BKtxeFuVpQ+1c/tf/NXhE0XKXltthS6 17 | vS4ASalTPSj/XeIEKGNr9td4DY8l7d1u8zXmQnqNpkm9CafekDXKeSESb0kr4jk4 18 | 3bqnpuuwyORUFluWbpAGAZhFSNSvM7c22k+LKCCIfv5S4Mq7NQEVqpmIoltON87X 19 | JbZcG/Q9hDGYoEEtLhy5Rg30Ur8P2fYb9tI1uD2nQdXyrXr+YGCBLQgj 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /tests/ssl/gallery.test.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEApD4bJnE2jklhlp6AUVHrsORApNYk2rT1jjPBD0NAS4o00cNx 3 | axpGEr1WBI2nhizLnRFuRwSx7XJN1/YLi+BJ6HlRhjK/rnTY4h3D5XLTiQTuOMFR 4 | 0OU7vZhs4vWts1MmtaWmuAnu2nudVMlLU3IcFVapos8RDaTupD1CsxfiOurqN7Dd 5 | GCNth8lobdgfZ2NdQt5XSCAVit64wFnVJZG9JduMRh3aAHoz9gQDqXLdnVJCbS4n 6 | DkaRfsRSdPIeaTK7FrglCWIrDSMLuNbvMwfJhbTafU0e2Un1vPyGLaYue/ZPsMl7 7 | rgf/yHqmgCegOEJNzPNYDJjJ3n/2Eh4TrhLhLQIDAQABAoIBAG8F4Hp6+yLxrbsQ 8 | B7Nwyf64PI5wh1ckDf2DNJTpo/OsSAonsGpyxz1xqUzIqJ6Fd1oEZ2QwY0ocA0zh 9 | 8GosQimYoKgzc+ks6VLoRfy7Qw0TPSk2XOT1oA2IlMpSwok+wyL1CB7N8BzWhMLE 10 | k1uj/iHahC8fA+sSO5LgKk8/7Nhf0HLP+LTPgoE9ZTitwF/pedVqbJ9UdSA0GQTR 11 | 3GiWcGwtwzSDkK4Yx2CaBWjQjpasWccI8rVX98O7jHNQa8ihmb749Q65RWu+Qj1l 12 | GNRgrqDj2ovXElurHGNpkHezAIEgLMzdqymVSE7iWT4523uXGlL8f3VSp79Z8xjq 13 | NUeCZqECgYEA1EBpuKbWmH1zBjhn1y8VK4sVRi5C4eGc/vDCSTMq6i2/iqvILYVf 14 | Ik3CXq+O1wYZpnv1u2tU7XTNY5pps9Ln2JI7NeB/RP1Qx3IRBDw5xOS12KzyXEbA 15 | eoKq3I1IlsvZpp7rJX+E4iRopWau3tcWxSloBlKVhd7go2Wy9+hQ4kUCgYEAxhh3 16 | 0ZbsdbYgMFO2jlQXDkiqj6M3YvYci3GImJIVgEdlrozt9Rjsfv+peCnk6FUVtw2w 17 | B0hjd8+UwLBVBrha/lgdkC2JRtksrH2a0zv0dSc7PEXBfGjXY4hF9KwX8nJaAcuC 18 | vq/nt4l2LSz8UdH0UblLzDk2NbU33AEBJ+LcZckCgYBfXzIiDpWOn4/305CVWEqi 19 | 7jP4/QCAjugEhhpG2f8Gitfub5HbInrE5zvXX/tHEy6jaD/anzfiGwHPJ6snajVT 20 | UyHX67BER67sb4zF4mny1vH/Enc0j3YdlmlAARwE5kQ+Ylo21z3Wf5Wl/KXG688H 21 | eLuNW75kEOnPgfsQ+gqeIQKBgQCZgu6fgii+U6QAL29iPrIbRJ7AbwiF7RlTymjD 22 | azm0Th/zM41YCbeD91X6I+UbUoxgbgACtMs4Vqn85OkPKyCAyAIo79DZEYV4KmIn 23 | 3edkPkG+RIRbh9eab0lfAVoRbSJpD6/DXAfiiGkHOzxRn7WI+2JM4e0rk8K1ACZI 24 | JZsMWQKBgQCjlcMmZGY8mlAULBSCfNztLGVeCdu0PLA0Xw9UMPgE2x0c/lMdPHC6 25 | 3Ne4vnDw1xdhy+7aIxhq6OPMAadKn08+jStHpUWjNioX7ReqAKQE6uOMklF0lzyj 26 | jYN8D+ZCsU6BiTAlCtcavSn47NZUAHb/2VepPkwjfbfZBcBX9v0bdg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/ssl/gallery.test.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDVjCCAj6gAwIBAgIUOyJRr54pqXviO701Fpo5CZwEDSYwDQYJKoZIhvcNAQEL 3 | BQAwQjELMAkGA1UEBhMCQ1oxDzANBgNVBAgMBlByYWd1ZTEPMA0GA1UEBwwGUHJh 4 | Z3VlMREwDwYDVQQKDAhERVZDRVJUUzAeFw0yMTA5MjAyMTA0MjhaFw0zMTA5MTgy 5 | MTA0MjhaMFsxCzAJBgNVBAYTAkNaMQ8wDQYDVQQIDAZQcmFndWUxDzANBgNVBAcM 6 | BlByYWd1ZTERMA8GA1UECgwIREVWQ0VSVFMxFzAVBgNVBAMMDiouZ2FsbGVyeS50 7 | ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApD4bJnE2jklhlp6A 8 | UVHrsORApNYk2rT1jjPBD0NAS4o00cNxaxpGEr1WBI2nhizLnRFuRwSx7XJN1/YL 9 | i+BJ6HlRhjK/rnTY4h3D5XLTiQTuOMFR0OU7vZhs4vWts1MmtaWmuAnu2nudVMlL 10 | U3IcFVapos8RDaTupD1CsxfiOurqN7DdGCNth8lobdgfZ2NdQt5XSCAVit64wFnV 11 | JZG9JduMRh3aAHoz9gQDqXLdnVJCbS4nDkaRfsRSdPIeaTK7FrglCWIrDSMLuNbv 12 | MwfJhbTafU0e2Un1vPyGLaYue/ZPsMl7rgf/yHqmgCegOEJNzPNYDJjJ3n/2Eh4T 13 | rhLhLQIDAQABoyswKTAnBgNVHREEIDAeggxnYWxsZXJ5LnRlc3SCDiouZ2FsbGVy 14 | eS50ZXN0MA0GCSqGSIb3DQEBCwUAA4IBAQBQTc/sUV9xnvkkui0RHFRch7CJ++ti 15 | 1lgCj8RTTaISwkmzNnidV8K1ic5yflQzK28xx391VgIsxJS2VyQ4gKXLLwZkBGBX 16 | W+yRZoNo4hq7g0Q0vxvzYbk6lFxmmCPU8BKtxeFuVpQ+1c/tf/NXhE0XKXltthS6 17 | vS4ASalTPSj/XeIEKGNr9td4DY8l7d1u8zXmQnqNpkm9CafekDXKeSESb0kr4jk4 18 | 3bqnpuuwyORUFluWbpAGAZhFSNSvM7c22k+LKCCIfv5S4Mq7NQEVqpmIoltON87X 19 | JbZcG/Q9hDGYoEEtLhy5Rg30Ur8P2fYb9tI1uD2nQdXyrXr+YGCBLQgj 20 | -----END CERTIFICATE----- 21 | -----BEGIN RSA PRIVATE KEY----- 22 | MIIEpAIBAAKCAQEApD4bJnE2jklhlp6AUVHrsORApNYk2rT1jjPBD0NAS4o00cNx 23 | axpGEr1WBI2nhizLnRFuRwSx7XJN1/YLi+BJ6HlRhjK/rnTY4h3D5XLTiQTuOMFR 24 | 0OU7vZhs4vWts1MmtaWmuAnu2nudVMlLU3IcFVapos8RDaTupD1CsxfiOurqN7Dd 25 | GCNth8lobdgfZ2NdQt5XSCAVit64wFnVJZG9JduMRh3aAHoz9gQDqXLdnVJCbS4n 26 | DkaRfsRSdPIeaTK7FrglCWIrDSMLuNbvMwfJhbTafU0e2Un1vPyGLaYue/ZPsMl7 27 | rgf/yHqmgCegOEJNzPNYDJjJ3n/2Eh4TrhLhLQIDAQABAoIBAG8F4Hp6+yLxrbsQ 28 | B7Nwyf64PI5wh1ckDf2DNJTpo/OsSAonsGpyxz1xqUzIqJ6Fd1oEZ2QwY0ocA0zh 29 | 8GosQimYoKgzc+ks6VLoRfy7Qw0TPSk2XOT1oA2IlMpSwok+wyL1CB7N8BzWhMLE 30 | k1uj/iHahC8fA+sSO5LgKk8/7Nhf0HLP+LTPgoE9ZTitwF/pedVqbJ9UdSA0GQTR 31 | 3GiWcGwtwzSDkK4Yx2CaBWjQjpasWccI8rVX98O7jHNQa8ihmb749Q65RWu+Qj1l 32 | GNRgrqDj2ovXElurHGNpkHezAIEgLMzdqymVSE7iWT4523uXGlL8f3VSp79Z8xjq 33 | NUeCZqECgYEA1EBpuKbWmH1zBjhn1y8VK4sVRi5C4eGc/vDCSTMq6i2/iqvILYVf 34 | Ik3CXq+O1wYZpnv1u2tU7XTNY5pps9Ln2JI7NeB/RP1Qx3IRBDw5xOS12KzyXEbA 35 | eoKq3I1IlsvZpp7rJX+E4iRopWau3tcWxSloBlKVhd7go2Wy9+hQ4kUCgYEAxhh3 36 | 0ZbsdbYgMFO2jlQXDkiqj6M3YvYci3GImJIVgEdlrozt9Rjsfv+peCnk6FUVtw2w 37 | B0hjd8+UwLBVBrha/lgdkC2JRtksrH2a0zv0dSc7PEXBfGjXY4hF9KwX8nJaAcuC 38 | vq/nt4l2LSz8UdH0UblLzDk2NbU33AEBJ+LcZckCgYBfXzIiDpWOn4/305CVWEqi 39 | 7jP4/QCAjugEhhpG2f8Gitfub5HbInrE5zvXX/tHEy6jaD/anzfiGwHPJ6snajVT 40 | UyHX67BER67sb4zF4mny1vH/Enc0j3YdlmlAARwE5kQ+Ylo21z3Wf5Wl/KXG688H 41 | eLuNW75kEOnPgfsQ+gqeIQKBgQCZgu6fgii+U6QAL29iPrIbRJ7AbwiF7RlTymjD 42 | azm0Th/zM41YCbeD91X6I+UbUoxgbgACtMs4Vqn85OkPKyCAyAIo79DZEYV4KmIn 43 | 3edkPkG+RIRbh9eab0lfAVoRbSJpD6/DXAfiiGkHOzxRn7WI+2JM4e0rk8K1ACZI 44 | JZsMWQKBgQCjlcMmZGY8mlAULBSCfNztLGVeCdu0PLA0Xw9UMPgE2x0c/lMdPHC6 45 | 3Ne4vnDw1xdhy+7aIxhq6OPMAadKn08+jStHpUWjNioX7ReqAKQE6uOMklF0lzyj 46 | jYN8D+ZCsU6BiTAlCtcavSn47NZUAHb/2VepPkwjfbfZBcBX9v0bdg== 47 | -----END RSA PRIVATE KEY----- 48 | -------------------------------------------------------------------------------- /tests/test-all-path.yml: -------------------------------------------------------------------------------- 1 | nginxConfDir: /etc/nginx/sites-available 2 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery 3 | serverName: gallery.test 4 | galleryDir: /var/www/static-nginx-gallery/example 5 | path: sub-path 6 | thumbnails: /etc/nginx/apps/static-nginx-gallery/cache 7 | videoThumbnails: /etc/nginx/apps/static-nginx-gallery/cache 8 | subtitles: 9 | convertSrtToVtt: true 10 | ssl: 11 | certificate: /var/www/static-nginx-gallery/ssl/gallery.test.crt 12 | certificateKey: /var/www/static-nginx-gallery/ssl/gallery.test.key 13 | -------------------------------------------------------------------------------- /tests/test-all.yml: -------------------------------------------------------------------------------- 1 | nginxConfDir: /etc/nginx/sites-available 2 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery 3 | serverName: gallery.test 4 | galleryDir: /var/www/static-nginx-gallery/example 5 | thumbnails: /etc/nginx/apps/static-nginx-gallery/cache 6 | videoThumbnails: /etc/nginx/apps/static-nginx-gallery/cache 7 | subtitles: 8 | convertSrtToVtt: true 9 | ssl: 10 | certificate: /var/www/static-nginx-gallery/ssl/gallery.test.crt 11 | certificateKey: /var/www/static-nginx-gallery/ssl/gallery.test.key 12 | -------------------------------------------------------------------------------- /tests/test-favicon.yml: -------------------------------------------------------------------------------- 1 | nginxConfDir: /etc/nginx/sites-available/ 2 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery/ 3 | serverName: gallery.test 4 | galleryDir: /var/www/static-nginx-gallery/example 5 | favicon: /var/www/static-nginx-gallery/assets/favicon-white.png 6 | ssl: 7 | certificate: /var/www/static-nginx-gallery/ssl/gallery.test.crt 8 | certificateKey: /var/www/static-nginx-gallery/ssl/gallery.test.key 9 | -------------------------------------------------------------------------------- /tests/test-http-port.yml: -------------------------------------------------------------------------------- 1 | nginxConfDir: /etc/nginx/sites-available/ 2 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery/ 3 | serverName: gallery.test 4 | galleryDir: /var/www/static-nginx-gallery/example 5 | port: 8080 6 | -------------------------------------------------------------------------------- /tests/test-http.yml: -------------------------------------------------------------------------------- 1 | nginxConfDir: /etc/nginx/sites-available/ 2 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery/ 3 | serverName: gallery.test 4 | galleryDir: /var/www/static-nginx-gallery/example 5 | -------------------------------------------------------------------------------- /tests/test-httpauth.yml: -------------------------------------------------------------------------------- 1 | nginxConfDir: /etc/nginx/sites-available/ 2 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery/ 3 | serverName: gallery.test 4 | galleryDir: /var/www/static-nginx-gallery/example 5 | ssl: 6 | certificate: /var/www/static-nginx-gallery/ssl/gallery.test.crt 7 | certificateKey: /var/www/static-nginx-gallery/ssl/gallery.test.key 8 | httpAuth: /var/www/static-nginx-gallery/assets/htpasswd 9 | -------------------------------------------------------------------------------- /tests/test-https-httpport.yml: -------------------------------------------------------------------------------- 1 | nginxConfDir: /etc/nginx/sites-available/ 2 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery/ 3 | serverName: gallery.test 4 | galleryDir: /var/www/static-nginx-gallery/example 5 | ssl: 6 | certificate: /var/www/static-nginx-gallery/ssl/gallery.test.crt 7 | certificateKey: /var/www/static-nginx-gallery/ssl/gallery.test.key 8 | httpPort: 8080 9 | -------------------------------------------------------------------------------- /tests/test-https-letsencrypt.yml: -------------------------------------------------------------------------------- 1 | nginxConfDir: /etc/nginx/sites-available/ 2 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery/ 3 | serverName: gallery.test 4 | galleryDir: /var/www/static-nginx-gallery/example 5 | ssl: 6 | certificate: /var/www/static-nginx-gallery/ssl/gallery.test.crt 7 | certificateKey: /var/www/static-nginx-gallery/ssl/gallery.test.key 8 | letsEncrypt: /var/www/letsencrypt 9 | -------------------------------------------------------------------------------- /tests/test-https-noredirect.yml: -------------------------------------------------------------------------------- 1 | nginxConfDir: /etc/nginx/sites-available/ 2 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery/ 3 | serverName: gallery.test 4 | galleryDir: /var/www/static-nginx-gallery/example 5 | ssl: 6 | certificate: /var/www/static-nginx-gallery/ssl/gallery.test.crt 7 | certificateKey: /var/www/static-nginx-gallery/ssl/gallery.test.key 8 | redirectHttpToHttps: false 9 | -------------------------------------------------------------------------------- /tests/test-https-port.yml: -------------------------------------------------------------------------------- 1 | nginxConfDir: /etc/nginx/sites-available/ 2 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery/ 3 | serverName: gallery.test 4 | galleryDir: /var/www/static-nginx-gallery/example 5 | port: 5433 6 | ssl: 7 | certificate: /var/www/static-nginx-gallery/ssl/gallery.test.crt 8 | certificateKey: /var/www/static-nginx-gallery/ssl/gallery.test.key 9 | -------------------------------------------------------------------------------- /tests/test-light-theme.yml: -------------------------------------------------------------------------------- 1 | nginxConfDir: /etc/nginx/sites-available/ 2 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery/ 3 | serverName: gallery.test 4 | galleryDir: /var/www/static-nginx-gallery/example 5 | lightTheme: true 6 | ssl: 7 | certificate: /var/www/static-nginx-gallery/ssl/gallery.test.crt 8 | certificateKey: /var/www/static-nginx-gallery/ssl/gallery.test.key 9 | -------------------------------------------------------------------------------- /tests/test-simple.yml: -------------------------------------------------------------------------------- 1 | nginxConfDir: /etc/nginx/sites-available/ 2 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery/ 3 | serverName: gallery.test 4 | galleryDir: /var/www/static-nginx-gallery/example 5 | ssl: 6 | certificate: /var/www/static-nginx-gallery/ssl/gallery.test.crt 7 | certificateKey: /var/www/static-nginx-gallery/ssl/gallery.test.key 8 | -------------------------------------------------------------------------------- /tests/test-subtitles.yml: -------------------------------------------------------------------------------- 1 | nginxConfDir: /etc/nginx/sites-available/ 2 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery/ 3 | serverName: gallery.test 4 | galleryDir: /var/www/static-nginx-gallery/example 5 | subtitles: 6 | convertSrtToVtt: true 7 | ssl: 8 | certificate: /var/www/static-nginx-gallery/ssl/gallery.test.crt 9 | certificateKey: /var/www/static-nginx-gallery/ssl/gallery.test.key 10 | -------------------------------------------------------------------------------- /tests/test-thumbnails-cache.yml: -------------------------------------------------------------------------------- 1 | nginxConfDir: /etc/nginx/sites-available 2 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery 3 | serverName: gallery.test 4 | galleryDir: /var/www/static-nginx-gallery/example 5 | thumbnails: /etc/nginx/apps/static-nginx-gallery/cache 6 | ssl: 7 | certificate: /var/www/static-nginx-gallery/ssl/gallery.test.crt 8 | certificateKey: /var/www/static-nginx-gallery/ssl/gallery.test.key 9 | -------------------------------------------------------------------------------- /tests/test-thumbnails.yml: -------------------------------------------------------------------------------- 1 | nginxConfDir: /etc/nginx/sites-available/ 2 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery/ 3 | serverName: gallery.test 4 | galleryDir: /var/www/static-nginx-gallery/example 5 | thumbnails: true 6 | ssl: 7 | certificate: /var/www/static-nginx-gallery/ssl/gallery.test.crt 8 | certificateKey: /var/www/static-nginx-gallery/ssl/gallery.test.key 9 | -------------------------------------------------------------------------------- /tests/test-title.yml: -------------------------------------------------------------------------------- 1 | nginxConfDir: /etc/nginx/sites-available/ 2 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery/ 3 | serverName: gallery.test 4 | galleryDir: /var/www/static-nginx-gallery/example 5 | title: 'Files' 6 | ssl: 7 | certificate: /var/www/static-nginx-gallery/ssl/gallery.test.crt 8 | certificateKey: /var/www/static-nginx-gallery/ssl/gallery.test.key 9 | -------------------------------------------------------------------------------- /tests/test-video-thumbnails.yml: -------------------------------------------------------------------------------- 1 | nginxConfDir: /etc/nginx/sites-available/ 2 | nginxAppDir: /etc/nginx/apps/static-nginx-gallery/ 3 | serverName: gallery.test 4 | galleryDir: /var/www/static-nginx-gallery/example 5 | videoThumbnails: /etc/nginx/apps/static-nginx-gallery/cache 6 | ssl: 7 | certificate: /var/www/static-nginx-gallery/ssl/gallery.test.crt 8 | certificateKey: /var/www/static-nginx-gallery/ssl/gallery.test.key 9 | --------------------------------------------------------------------------------