Expand gallery functionality to automatic generation of index, slideshow and map files
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
layout: "post"
|
layout: default
|
||||||
---
|
---
|
||||||
<section itemscope itemtype="http://schema.org/ImageGallery">
|
<section itemscope itemtype="http://schema.org/ImageGallery">
|
||||||
<header>
|
<header>
|
||||||
@@ -11,4 +11,7 @@ layout: "post"
|
|||||||
<section class="post-content">
|
<section class="post-content">
|
||||||
{{ content }}
|
{{ content }}
|
||||||
</section>
|
</section>
|
||||||
|
{% if false != page.comments %}
|
||||||
|
{% include disqus.html %}
|
||||||
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
layout: "post"
|
layout: post
|
||||||
---
|
---
|
||||||
{% capture gallery %}{{ page.url|replace:'/map.html',''|remove_first:'/' }}{% endcapture %}
|
{% capture gallery %}{{ page.url|replace:'/map.html',''|remove_first:'/' }}{% endcapture %}
|
||||||
<div id="googlemap" style="height:400px;margin-top:8px;"></div>
|
<div id="googlemap" style="height:400px;margin-top:8px;"></div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
layout: "post"
|
layout: post
|
||||||
---
|
---
|
||||||
<section itemscope itemtype="http://schema.org/Photograph">
|
<section itemscope itemtype="http://schema.org/Photograph">
|
||||||
<header>
|
<header>
|
||||||
|
|||||||
@@ -62,7 +62,6 @@
|
|||||||
|
|
||||||
<!-- Root element of PhotoSwipe. Must have class pswp. -->
|
<!-- Root element of PhotoSwipe. Must have class pswp. -->
|
||||||
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
|
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true">
|
||||||
|
|
||||||
<!-- Background of PhotoSwipe.
|
<!-- Background of PhotoSwipe.
|
||||||
It's a separate element, as animating opacity is faster than rgba(). -->
|
It's a separate element, as animating opacity is faster than rgba(). -->
|
||||||
<div class="pswp__bg"></div>
|
<div class="pswp__bg"></div>
|
||||||
@@ -81,19 +80,13 @@
|
|||||||
|
|
||||||
<!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
|
<!-- Default (PhotoSwipeUI_Default) interface on top of sliding area. Can be changed. -->
|
||||||
<div class="pswp__ui pswp__ui--hidden">
|
<div class="pswp__ui pswp__ui--hidden">
|
||||||
|
|
||||||
<div class="pswp__top-bar">
|
<div class="pswp__top-bar">
|
||||||
|
|
||||||
<!-- Controls are self-explanatory. Order can be changed. -->
|
<!-- Controls are self-explanatory. Order can be changed. -->
|
||||||
|
|
||||||
<div class="pswp__counter"></div>
|
<div class="pswp__counter"></div>
|
||||||
|
|
||||||
<button class="pswp__button pswp__button--close" title="Close (Esc)"></button>
|
<button class="pswp__button pswp__button--close" title="Close (Esc)"></button>
|
||||||
|
|
||||||
<button class="pswp__button pswp__button--share" title="Share"></button>
|
<button class="pswp__button pswp__button--share" title="Share"></button>
|
||||||
|
|
||||||
<button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
|
<button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
|
||||||
|
|
||||||
<button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>
|
<button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button>
|
||||||
|
|
||||||
<!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
|
<!-- Preloader demo http://codepen.io/dimsemenov/pen/yyBWoR -->
|
||||||
@@ -120,11 +113,8 @@
|
|||||||
<div class="pswp__caption">
|
<div class="pswp__caption">
|
||||||
<div class="pswp__caption__center"></div>
|
<div class="pswp__caption__center"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|||||||
@@ -2,13 +2,10 @@
|
|||||||
layout: default
|
layout: default
|
||||||
---
|
---
|
||||||
<article class="post">
|
<article class="post">
|
||||||
|
|
||||||
<header class="post-header">
|
<header class="post-header">
|
||||||
<h1 class="post-title">{{ page.title }}</h1>
|
<h1 class="post-title">{{ page.title }}</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="post-content">
|
<div class="post-content">
|
||||||
{{ content }}
|
{{ content }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -11,9 +11,6 @@
|
|||||||
{% if page.code %}
|
{% if page.code %}
|
||||||
<a href="{{ page.code }}"><img alt="code" height="49" src="{{ site.static_prefix }}/icon/code.png" width="49" /></a>
|
<a href="{{ page.code }}"><img alt="code" height="49" src="{{ site.static_prefix }}/icon/code.png" width="49" /></a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
<div class="social screen-only">
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<h1>
|
<h1>
|
||||||
<span itemprop="name">{{ page.title }}</span>
|
<span itemprop="name">{{ page.title }}</span>
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
REM @echo off
|
set root=%~dp0..
|
||||||
set root="%~dp0.."
|
|
||||||
php -dmemory_limit=1G %root%/_scripts/convert.php ^
|
php -dmemory_limit=1G %root%/_scripts/convert.php ^
|
||||||
2016-todo ^
|
%1 ^
|
||||||
|
%2 ^
|
||||||
%root%/asset/gallery ^
|
%root%/asset/gallery ^
|
||||||
%root%/gallery ^
|
%root%/gallery ^
|
||||||
--export 1920x1080 ^
|
--export 1920x1080 ^
|
||||||
--export 200x200 ^
|
--export 200x200 ^
|
||||||
--export 96x96 ^
|
--export 96x96 ^
|
||||||
--export 640w ^
|
--export 640w
|
||||||
--importdir %1
|
|
||||||
|
|||||||
@@ -9,29 +9,14 @@ use Symfony\Component\Console\Input\InputOption;
|
|||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
function writeMetaYaml($sDir) {
|
|
||||||
$aMeta['dir'] = rtrim($sDir, '"\'/\\');
|
|
||||||
$aMeta['files'] = array();
|
|
||||||
if (file_exists($aMeta['dir'])) {
|
|
||||||
$aFiles = glob($aMeta['dir'] . '/*.jpg');
|
|
||||||
foreach ($aFiles as $sFile) {
|
|
||||||
$aMeta['files'][basename($sFile)] = ['title' => '', 'comment' => ''];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$sYaml = str_replace("''", null, Yaml::dump($aMeta, 4, 2));
|
|
||||||
file_put_contents($sDir . '/meta.yaml', $sYaml);
|
|
||||||
}
|
|
||||||
// writeMetaYaml('C:\Users\Rik\Downloads\Blog\jekyll-gallery\in');
|
|
||||||
|
|
||||||
$oConsole = new Application();
|
$oConsole = new Application();
|
||||||
|
|
||||||
$oConsole
|
$oConsole
|
||||||
->register('run')
|
->register('run')
|
||||||
->setDefinition([
|
->setDefinition([
|
||||||
new InputOption('export', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Target image export sizes'),
|
new InputOption('export', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Target image export sizes'),
|
||||||
new InputOption('layout', null, InputOption::VALUE_REQUIRED, 'Rendering layout for individual images', 'gallery-photo'),
|
new InputOption('layout', null, InputOption::VALUE_REQUIRED, 'Rendering layout for individual images', 'gallery-photo'),
|
||||||
new InputOption('importdir', null, InputOption::VALUE_REQUIRED, 'Directory to scan for images'),
|
new InputArgument('name', InputArgument::REQUIRED, 'Gallery name'),
|
||||||
new InputArgument('name', null, InputArgument::REQUIRED, 'Gallery name'),
|
new InputArgument('dir', InputArgument::REQUIRED, 'Directory to scan for images'),
|
||||||
new InputArgument('assetdir', InputArgument::OPTIONAL, 'Asset directory for exported images', 'asset/gallery'),
|
new InputArgument('assetdir', InputArgument::OPTIONAL, 'Asset directory for exported images', 'asset/gallery'),
|
||||||
new InputArgument('mdowndir', InputArgument::OPTIONAL, 'Markdown directory for dumping individual photo details', 'gallery'),
|
new InputArgument('mdowndir', InputArgument::OPTIONAL, 'Markdown directory for dumping individual photo details', 'gallery'),
|
||||||
])
|
])
|
||||||
@@ -45,9 +30,9 @@ $oConsole
|
|||||||
function (InputInterface $oInput, OutputInterface $oOutput) {
|
function (InputInterface $oInput, OutputInterface $oOutput) {
|
||||||
// Get input arguments and options
|
// Get input arguments and options
|
||||||
$sGallery = $oInput->getArgument('name');
|
$sGallery = $oInput->getArgument('name');
|
||||||
|
$sDir = realpath(rtrim($oInput->getArgument('dir'), '/\\'));
|
||||||
$sAssetPath = $oInput->getArgument('assetdir') . '/' . $sGallery;
|
$sAssetPath = $oInput->getArgument('assetdir') . '/' . $sGallery;
|
||||||
$sRenderPath = $oInput->getArgument('mdowndir') . '/' . $sGallery;
|
$sRenderPath = $oInput->getArgument('mdowndir') . '/' . $sGallery;
|
||||||
$sImportDir = $oInput->getOption('importdir');
|
|
||||||
$sLayout = $oInput->getOption('layout');
|
$sLayout = $oInput->getOption('layout');
|
||||||
$sExports = $oInput->getOption('export');
|
$sExports = $oInput->getOption('export');
|
||||||
|
|
||||||
@@ -60,37 +45,35 @@ $oConsole
|
|||||||
if (!is_dir($sRenderPath)) {
|
if (!is_dir($sRenderPath)) {
|
||||||
mkdir($sRenderPath, 0700, true);
|
mkdir($sRenderPath, 0700, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isset($sImportDir)) {
|
// Check directory and presence of YAML file
|
||||||
// Use provided directory
|
if (!is_dir($sDir)) {
|
||||||
$sImportDir = rtrim($oInput->getOption('importdir'), '/\\');
|
|
||||||
} else {
|
|
||||||
// Get directory and metadata from yaml
|
|
||||||
$sStdin = stream_get_contents(STDIN);
|
|
||||||
$aYaml = Yaml::parse($sStdin);
|
|
||||||
$sImportDir = rtrim($aYaml['dir'], '"\'/\\');
|
|
||||||
$aMeta = $aYaml['files'];
|
|
||||||
if (!isset($aYaml['all'])) {
|
|
||||||
$aFiles = array_keys($aYaml['files']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$sImportDir = realpath($sImportDir);
|
|
||||||
if (!is_dir($sImportDir)) {
|
|
||||||
$oOutput->writeln('<error>Import directory does not exist</error>');
|
$oOutput->writeln('<error>Import directory does not exist</error>');
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
$sYamlFile = sprintf('%s/meta.yaml', $sDir);
|
||||||
// Scan import directory for images
|
if (!file_exists($sYamlFile)) {
|
||||||
if (!isset($aFiles)) {
|
$oOutput->writeln('<error>No meta.yaml in directory</error>');
|
||||||
$aFiles = array_map('basename', glob($sImportDir . '/*.jpg'));
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse YAML file
|
||||||
|
$aYaml = Yaml::parse(file_get_contents($sYamlFile));
|
||||||
|
if (!isset($aYaml['gallery']) || !isset($aYaml['files'])) {
|
||||||
|
$oOutput->writeln('<error>Invalid YAML file</error>');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$aGallery = $aYaml['gallery'];
|
||||||
|
$aMeta = $aYaml['files'];
|
||||||
|
$aFiles = array_keys($aYaml['files']);
|
||||||
|
|
||||||
// Loop over files
|
// Loop over files
|
||||||
|
$sHighlight = null;
|
||||||
$aPhotos = [];
|
$aPhotos = [];
|
||||||
foreach ($aFiles as $i => $sFile) {
|
foreach ($aFiles as $i => $sFile) {
|
||||||
// Build photo information
|
// Build photo information
|
||||||
$aPhoto = [
|
$aPhoto = [
|
||||||
'path' => $sImportDir . '/' . $sFile,
|
'path' => $sDir . '/' . $sFile,
|
||||||
'ordering' => $i,
|
'ordering' => $i,
|
||||||
'name' => isset($aMeta[$sFile]['name']) ? $aMeta[$sFile]['name'] : null,
|
'name' => isset($aMeta[$sFile]['name']) ? $aMeta[$sFile]['name'] : null,
|
||||||
'comment' => isset($aMeta[$sFile]['comment']) ? $aMeta[$sFile]['comment'] : null
|
'comment' => isset($aMeta[$sFile]['comment']) ? $aMeta[$sFile]['comment'] : null
|
||||||
@@ -102,6 +85,11 @@ $oConsole
|
|||||||
$aPhoto['id'] .= '-' . preg_replace('/(-| )+/', '-', preg_replace('/[^a-z0-9 ]/i', '-', preg_replace('/\'/', '', strtolower(preg_replace('/\p{Mn}/u', '', Normalizer::normalize($aPhoto['title'], Normalizer::FORM_KD))))));
|
$aPhoto['id'] .= '-' . preg_replace('/(-| )+/', '-', preg_replace('/[^a-z0-9 ]/i', '-', preg_replace('/\'/', '', strtolower(preg_replace('/\p{Mn}/u', '', Normalizer::normalize($aPhoto['title'], Normalizer::FORM_KD))))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if photo is highlighted
|
||||||
|
if (empty($sHighlight) && isset($aMeta[$sFile]['highlight'])) {
|
||||||
|
$sHighlight = $aPhoto['id'];
|
||||||
|
}
|
||||||
|
|
||||||
// Parse selected EXIF data
|
// Parse selected EXIF data
|
||||||
$aPhoto['exif'] = exif_read_data($aPhoto['path']);
|
$aPhoto['exif'] = exif_read_data($aPhoto['path']);
|
||||||
if (isset($aPhoto['exif']['GPSLongitude'])) {
|
if (isset($aPhoto['exif']['GPSLongitude'])) {
|
||||||
@@ -110,6 +98,8 @@ $oConsole
|
|||||||
'latitude' => coordinateToDegrees($aPhoto['exif']['GPSLatitude'], $aPhoto['exif']['GPSLatitudeRef']),
|
'latitude' => coordinateToDegrees($aPhoto['exif']['GPSLatitude'], $aPhoto['exif']['GPSLatitudeRef']),
|
||||||
'altitude' => fractionToFloat($aPhoto['exif']['GPSAltitude']),
|
'altitude' => fractionToFloat($aPhoto['exif']['GPSAltitude']),
|
||||||
'direction' => fractionToFloat($aPhoto['exif']['GPSImgDirection'])]);
|
'direction' => fractionToFloat($aPhoto['exif']['GPSImgDirection'])]);
|
||||||
|
$aLongitude[] = $aPhoto['longitude'];
|
||||||
|
$aLatitude[] = $aPhoto['latitude'];
|
||||||
}
|
}
|
||||||
$aPhoto['date'] = new DateTime($aPhoto['exif']['DateTimeOriginal']);
|
$aPhoto['date'] = new DateTime($aPhoto['exif']['DateTimeOriginal']);
|
||||||
$aPhotos[] = $aPhoto;
|
$aPhotos[] = $aPhoto;
|
||||||
@@ -121,7 +111,7 @@ $oConsole
|
|||||||
$aPhoto['sizes'] = [];
|
$aPhoto['sizes'] = [];
|
||||||
|
|
||||||
// Image exports
|
// Image exports
|
||||||
if (0 < count($sExports)) {
|
if (false || 0 < count($sExports)) {
|
||||||
$oSourceJpg = $oImagine->open($aPhoto['path']);
|
$oSourceJpg = $oImagine->open($aPhoto['path']);
|
||||||
if (isset($aPhoto['exif']['Orientation'])) {
|
if (isset($aPhoto['exif']['Orientation'])) {
|
||||||
switch ($aPhoto['exif']['Orientation']) {
|
switch ($aPhoto['exif']['Orientation']) {
|
||||||
@@ -182,7 +172,6 @@ $oConsole
|
|||||||
}
|
}
|
||||||
|
|
||||||
$sExportsize = $sExportImage->getSize();
|
$sExportsize = $sExportImage->getSize();
|
||||||
|
|
||||||
$aPhoto['sizes'][$sExport] = [
|
$aPhoto['sizes'][$sExport] = [
|
||||||
'width' => $sExportsize->getWidth(),
|
'width' => $sExportsize->getWidth(),
|
||||||
'height' => $sExportsize->getHeight(),
|
'height' => $sExportsize->getHeight(),
|
||||||
@@ -210,6 +199,10 @@ $oConsole
|
|||||||
'date' => $aPhoto['date']->format('Y-m-d H:i:s'),
|
'date' => $aPhoto['date']->format('Y-m-d H:i:s'),
|
||||||
'ordering' => $aPhoto['ordering']
|
'ordering' => $aPhoto['ordering']
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Keep track of album dates
|
||||||
|
$oDate = $i > 0 ? min($oDate, $aPhoto['date']) : $aPhoto['date'];
|
||||||
|
$oEndDate = $i > 0 ? max($oEndDate, $aPhoto['date']) : $aPhoto['date'];
|
||||||
|
|
||||||
if (isset($aPhoto['exif']['Make'])) {
|
if (isset($aPhoto['exif']['Make'])) {
|
||||||
$aMatter['exif'] = [
|
$aMatter['exif'] = [
|
||||||
@@ -247,18 +240,87 @@ $oConsole
|
|||||||
$iSurfaceB = $aB['width'] * $aB['height'];
|
$iSurfaceB = $aB['width'] * $aB['height'];
|
||||||
return $iSurfaceA == $iSurfaceB
|
return $iSurfaceA == $iSurfaceB
|
||||||
? 0
|
? 0
|
||||||
: (($iSurfaceA > $iSurfaceB)? -1 : 1);
|
: (($iSurfaceA > $iSurfaceB) ? -1 : 1);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// Write Markdown file
|
// Write photo Markdown file
|
||||||
file_put_contents(
|
file_put_contents(
|
||||||
$sRenderPath . '/' . $aPhoto['id'] . '.md',
|
$sRenderPath . '/' . $aPhoto['id'] . '.md',
|
||||||
'---' . "\n" . Yaml::dump($aMatter, 4, 2) . '---' . "\n" . ((!empty($aPhoto['comment'])) ? ($aPhoto['comment'] . "\n") : '')
|
'---' . "\n" . yamlDump($aMatter) . '---' . "\n" . (empty($aPhoto['comment']) ? '' : $aPhoto['comment'] . "\n")
|
||||||
);
|
);
|
||||||
|
$oOutput->writeln(' done');
|
||||||
$oOutput->writeln(' done');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write slideshow
|
||||||
|
$bSlideshow = isset($aGallery['slideshow']) ? $aGallery['slideshow'] : true;
|
||||||
|
if ($bSlideshow) {
|
||||||
|
$oOutput->write('<comment>slideshow</comment>');
|
||||||
|
$aMatter = [
|
||||||
|
'layout' => 'gallery-slideshow',
|
||||||
|
'title' => empty($aGallery['title']) ? '' : $aGallery['title']
|
||||||
|
];
|
||||||
|
file_put_contents(
|
||||||
|
$sRenderPath . '/slideshow.html',
|
||||||
|
'---' . "\n" . yamlDump($aMatter) . '---' . "\n"
|
||||||
|
);
|
||||||
|
$oOutput->writeln(' done');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write map
|
||||||
|
$bMap = count($aLongitude) > 0 && (isset($aGallery['map']) ? $aGallery['map'] : true);
|
||||||
|
if ($bMap) {
|
||||||
|
$oOutput->write('<comment>map</comment>');
|
||||||
|
$aMatter = [
|
||||||
|
'layout' => 'gallery-map',
|
||||||
|
'title' => empty($aGallery['title']) ? '' : $aGallery['title'],
|
||||||
|
'gallery_map' => [
|
||||||
|
'latitude' => array_sum($aLatitude) / count($aLatitude),
|
||||||
|
'longitude' => array_sum($aLongitude) / count($aLongitude),
|
||||||
|
'zoom' => 8
|
||||||
|
]
|
||||||
|
];
|
||||||
|
file_put_contents(
|
||||||
|
$sRenderPath . '/map.html',
|
||||||
|
'---' . "\n" . yamlDump($aMatter) . '---' . "\n"
|
||||||
|
);
|
||||||
|
$oOutput->writeln(' done');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write gallery index
|
||||||
|
$oOutput->write('<comment>index</comment>');
|
||||||
|
$aLinks = [];
|
||||||
|
if ($bSlideshow) {
|
||||||
|
$aLinks[] = '<a href="slideshow.html">[ slideshow ]</a>';
|
||||||
|
}
|
||||||
|
if ($bMap) {
|
||||||
|
$aLinks[] = '<a href="map.html">[ map ]</a>';
|
||||||
|
}
|
||||||
|
$aMatter = [
|
||||||
|
'layout' => 'gallery-list',
|
||||||
|
'title' => empty($aGallery['title']) ? '' : $aGallery['title'],
|
||||||
|
'highlight_photo' => empty($sHighlight) ? $aPhotos[0]['id'] : $sHighlight,
|
||||||
|
'date' => $oDate->format('Y-m-d')
|
||||||
|
];
|
||||||
|
if ($oDate->diff($oEndDate)->d > 0) {
|
||||||
|
$aMatter['end_date'] = $oEndDate->format('Y-m-d');
|
||||||
|
}
|
||||||
|
|
||||||
|
$sContents = sprintf(
|
||||||
|
<<<EOF
|
||||||
|
<div itemprop="description">
|
||||||
|
<p>%s</p>
|
||||||
|
%s
|
||||||
|
</div>
|
||||||
|
{%% include gallery_list.html gallery='%s' %%}
|
||||||
|
EOF
|
||||||
|
, (empty($aGallery['description']) ? '' : $aGallery['description']), implode('·', $aLinks), $sGallery);
|
||||||
|
file_put_contents(
|
||||||
|
$sRenderPath . '/index.html',
|
||||||
|
'---' . "\n" . yamlDump($aMatter) . '---' . "\n" . $sContents
|
||||||
|
);
|
||||||
|
$oOutput->writeln(' done');
|
||||||
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -292,4 +354,8 @@ function fractionToFloat($sFraction) {
|
|||||||
? floatval($aParts[0]) / floatval($aParts[1])
|
? floatval($aParts[0]) / floatval($aParts[1])
|
||||||
: $aParts[0])
|
: $aParts[0])
|
||||||
: 0;
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function yamlDump($aData) {
|
||||||
|
return str_replace("'", null, Yaml::dump($aData, 4, 2));
|
||||||
}
|
}
|
||||||
2
_scripts/generate-gallery-yaml.bat
Normal file
2
_scripts/generate-gallery-yaml.bat
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
set root=%~dp0..
|
||||||
|
php %root%/_scripts/generate-gallery-yaml.php %1
|
||||||
23
_scripts/generate-gallery-yaml.php
Normal file
23
_scripts/generate-gallery-yaml.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
require __DIR__ . '/../vendor/autoload.php';
|
||||||
|
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
writeMetaYaml($argv[1]);
|
||||||
|
|
||||||
|
function writeMetaYaml($sDir) {
|
||||||
|
$sDir = rtrim($sDir, '"\'/\\');
|
||||||
|
$aMeta = [
|
||||||
|
'gallery' => ['name' => '', 'title' => '', 'description' => ''],
|
||||||
|
'files' => null
|
||||||
|
];
|
||||||
|
if (!is_dir($sDir)) {
|
||||||
|
die('Directory does not exist');
|
||||||
|
}
|
||||||
|
$aFiles = glob($sDir . '/*.jpg');
|
||||||
|
foreach ($aFiles as $sFile) {
|
||||||
|
$aMeta['files'][basename($sFile)] = ['title' => '', 'comment' => ''];
|
||||||
|
}
|
||||||
|
$sYaml = str_replace("''", null, Yaml::dump($aMeta, 4, 2));
|
||||||
|
file_put_contents($sDir . '/meta.yaml', $sYaml);
|
||||||
|
}
|
||||||
290
convert.php
290
convert.php
@@ -1,290 +0,0 @@
|
|||||||
<?php
|
|
||||||
require __DIR__ . '/vendor/autoload.php';
|
|
||||||
|
|
||||||
use Symfony\Component\Console\Application;
|
|
||||||
use Symfony\Component\Console\Input\ArgvInput;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
|
||||||
use Symfony\Component\Console\Input\InputArgument;
|
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
|
||||||
use Symfony\Component\Yaml\Yaml;
|
|
||||||
|
|
||||||
function writeMetaYaml($sDir) {
|
|
||||||
$aMeta['dir'] = rtrim($sDir, '"\'/\\');
|
|
||||||
$aMeta['files'] = array();
|
|
||||||
if (file_exists($aMeta['dir'])) {
|
|
||||||
$aFiles = glob($aMeta['dir'] . '/*.jpg');
|
|
||||||
foreach ($aFiles as $sFile) {
|
|
||||||
$aMeta['files'][basename($sFile)] = ['title' => '', 'comment' => ''];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$sYaml = str_replace("''", null, Yaml::dump($aMeta, 4, 2));
|
|
||||||
file_put_contents($sDir . '/meta.yaml', $sYaml);
|
|
||||||
}
|
|
||||||
// writeMetaYaml('C:\Users\Rik\Downloads\Blog\jekyll-gallery\in');
|
|
||||||
|
|
||||||
$oConsole = new Application();
|
|
||||||
|
|
||||||
$oConsole
|
|
||||||
->register('run')
|
|
||||||
->setDefinition([
|
|
||||||
new InputOption('export', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Target image export sizes'),
|
|
||||||
new InputOption('layout', null, InputOption::VALUE_REQUIRED, 'Rendering layout for individual images', 'gallery-photo'),
|
|
||||||
new InputOption('importdir', null, InputOption::VALUE_REQUIRED, 'Directory to scan for images'),
|
|
||||||
new InputArgument('name', null, InputArgument::REQUIRED, 'Gallery name'),
|
|
||||||
new InputArgument('assetdir', InputArgument::OPTIONAL, 'Asset directory for exported images', 'asset/gallery'),
|
|
||||||
new InputArgument('mdowndir', InputArgument::OPTIONAL, 'Markdown directory for dumping individual photo details', 'gallery'),
|
|
||||||
])
|
|
||||||
->setDescription('Parse a YAML-like gallery configuration and export it.')
|
|
||||||
->setHelp('
|
|
||||||
The export option will accept values like:
|
|
||||||
200x110 - photo will outset the boundary with the dimensions being 200x110
|
|
||||||
1280 - photo will inset with the largest dimension being 1280
|
|
||||||
')
|
|
||||||
->setCode(
|
|
||||||
function (InputInterface $oInput, OutputInterface $oOutput) {
|
|
||||||
// Get input arguments and options
|
|
||||||
$sGallery = $oInput->getArgument('name');
|
|
||||||
$sAssetPath = $oInput->getArgument('assetdir') . '/' . $sGallery;
|
|
||||||
$sRenderPath = $oInput->getArgument('mdowndir') . '/' . $sGallery;
|
|
||||||
$sImportDir = $oInput->getOption('importdir');
|
|
||||||
$sLayout = $oInput->getOption('layout');
|
|
||||||
$sExports = $oInput->getOption('export');
|
|
||||||
|
|
||||||
$oImagine = new Imagine\Gd\Imagine();
|
|
||||||
|
|
||||||
// Initialize directories
|
|
||||||
if (!is_dir($sAssetPath)) {
|
|
||||||
mkdir($sAssetPath, 0700, true);
|
|
||||||
}
|
|
||||||
if (!is_dir($sRenderPath)) {
|
|
||||||
mkdir($sRenderPath, 0700, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($sImportDir)) {
|
|
||||||
// Use provided directory
|
|
||||||
$sImportDir = rtrim($oInput->getOption('importdir'), '/\\');
|
|
||||||
} else {
|
|
||||||
// Get directory and metadata from yaml
|
|
||||||
$sStdin = stream_get_contents(STDIN);
|
|
||||||
$aYaml = Yaml::parse($sStdin);
|
|
||||||
$sImportDir = rtrim($aYaml['dir'], '"\'/\\');
|
|
||||||
$aMeta = $aYaml['files'];
|
|
||||||
if (!isset($aYaml['all'])) {
|
|
||||||
$aFiles = array_keys($aYaml['files']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan import directory for images
|
|
||||||
if (!isset($aFiles)) {
|
|
||||||
$aFiles = array_map('basename', glob($sImportDir . '/*.jpg'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop over files
|
|
||||||
$aPhotos = [];
|
|
||||||
foreach ($aFiles as $i => $sFile) {
|
|
||||||
// Build photo information
|
|
||||||
$aPhoto = [
|
|
||||||
'path' => $sImportDir . '/' . $sFile,
|
|
||||||
'ordering' => $i,
|
|
||||||
'name' => isset($aMeta[$sFile]['name']) ? $aMeta[$sFile]['name'] : null,
|
|
||||||
'comment' => isset($aMeta[$sFile]['comment']) ? $aMeta[$sFile]['comment'] : null
|
|
||||||
];
|
|
||||||
|
|
||||||
// Generate id from file contents
|
|
||||||
$aPhoto['id'] = substr(sha1_file($aPhoto['path']), 0, 7);
|
|
||||||
if (isset($aPhoto['title'])) {
|
|
||||||
$aPhoto['id'] .= '-' . preg_replace('/(-| )+/', '-', preg_replace('/[^a-z0-9 ]/i', '-', preg_replace('/\'/', '', strtolower(preg_replace('/\p{Mn}/u', '', Normalizer::normalize($aPhoto['title'], Normalizer::FORM_KD))))));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse selected EXIF data
|
|
||||||
$aPhoto['exif'] = exif_read_data($aPhoto['path']);
|
|
||||||
if (isset($aPhoto['exif']['GPSLongitude'])) {
|
|
||||||
$aPhoto = array_merge($aPhoto, [
|
|
||||||
'longitude' => coordinateToDegrees($aPhoto['exif']['GPSLongitude'], $aPhoto['exif']['GPSLongitudeRef']),
|
|
||||||
'latitude' => coordinateToDegrees($aPhoto['exif']['GPSLatitude'], $aPhoto['exif']['GPSLatitudeRef']),
|
|
||||||
'altitude' => fractionToFloat($aPhoto['exif']['GPSAltitude']),
|
|
||||||
'direction' => fractionToFloat($aPhoto['exif']['GPSImgDirection'])]);
|
|
||||||
}
|
|
||||||
$aPhoto['date'] = new DateTime($aPhoto['exif']['DateTimeOriginal']);
|
|
||||||
$aPhotos[] = $aPhoto;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manipulate
|
|
||||||
foreach ($aPhotos as $i => $aPhoto) {
|
|
||||||
$oOutput->write('<info>' . $aPhoto['id'] . '</info>');
|
|
||||||
$aPhoto['sizes'] = [];
|
|
||||||
|
|
||||||
// Image exports
|
|
||||||
if (0 < count($sExports)) {
|
|
||||||
$oSourceJpg = $oImagine->open($aPhoto['path']);
|
|
||||||
if (isset($aPhoto['exif']['Orientation'])) {
|
|
||||||
switch ($aPhoto['exif']['Orientation']) {
|
|
||||||
case 2:
|
|
||||||
$oSourceJpg->mirror();
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
$oSourceJpg->rotate(180);
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
$oSourceJpg->rotate(180)->mirror();
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
$oSourceJpg->rotate(90)->mirror();
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
$oSourceJpg->rotate(90);
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
$oSourceJpg->rotate(-90)->mirror();
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
$oSourceJpg->rotate(-90);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$oSourceSize = $oSourceJpg->getSize();
|
|
||||||
$oOutput->writeln(' [' . $oSourceSize->getWidth() . 'x' . $oSourceSize->getHeight() . ']...');
|
|
||||||
|
|
||||||
foreach ($sExports as $sExport) {
|
|
||||||
$oOutput->write(' <comment>' . $sExport . '</comment>');
|
|
||||||
|
|
||||||
if (false !== strpos($sExport, 'x')) {
|
|
||||||
list($iW, $iH) = explode('x', $sExport);
|
|
||||||
$sExportImage = $oSourceJpg->thumbnail(
|
|
||||||
new \Imagine\Image\Box($iW, $iH),
|
|
||||||
\Imagine\Image\ImageInterface::THUMBNAIL_OUTBOUND
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if ('w' == substr($sExport, -1)) {
|
|
||||||
$iX = (int) $sExport;
|
|
||||||
$iY = ($iX * $oSourceSize->getHeight()) / $oSourceSize->getWidth();
|
|
||||||
} elseif ('h' == substr($sExport, -1)) {
|
|
||||||
$iY = (int) $sExport;
|
|
||||||
$iX = ($iY * $oSourceSize->getWidth()) / $oSourceSize->getHeight();
|
|
||||||
} elseif ($oSourceSize->getWidth() == max($oSourceSize->getWidth(), $oSourceSize->getHeight())) {
|
|
||||||
$iX = (int) $sExport;
|
|
||||||
$iY = ($iX * $oSourceSize->getHeight()) / $oSourceSize->getWidth();
|
|
||||||
} elseif ($oSourceSize->getHeight() == max($oSourceSize->getWidth(), $oSourceSize->getHeight())) {
|
|
||||||
$iY = (int) $sExport;
|
|
||||||
$iX = ($iY * $oSourceSize->getWidth()) / $oSourceSize->getHeight();
|
|
||||||
}
|
|
||||||
$sExportImage = $oSourceJpg->thumbnail(
|
|
||||||
new \Imagine\Image\Box(ceil($iX), ceil($iY)),
|
|
||||||
\Imagine\Image\ImageInterface::THUMBNAIL_INSET
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$sExportsize = $sExportImage->getSize();
|
|
||||||
|
|
||||||
$aPhoto['sizes'][$sExport] = [
|
|
||||||
'width' => $sExportsize->getWidth(),
|
|
||||||
'height' => $sExportsize->getHeight(),
|
|
||||||
];
|
|
||||||
|
|
||||||
$oOutput->writeln(' [' . $sExportsize->getWidth() . 'x' . $sExportsize->getHeight() . ']');
|
|
||||||
$sExportPath = $sAssetPath . '/' . $aPhoto['id'] . '~' . $sExport . '.jpg';
|
|
||||||
|
|
||||||
// Write converted image
|
|
||||||
file_put_contents(
|
|
||||||
$sExportPath,
|
|
||||||
$sExportImage->get('jpeg', ['quality' => 90])
|
|
||||||
);
|
|
||||||
|
|
||||||
touch($sExportPath, $aPhoto['date']->getTimestamp());
|
|
||||||
$sExportImage = null;
|
|
||||||
}
|
|
||||||
$oSourceJpg = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$oOutput->write(' <comment>markdown</comment>');
|
|
||||||
$aMatter = [
|
|
||||||
'layout' => $sLayout,
|
|
||||||
'title' => isset($aPhoto['title']) ? $aPhoto['title'] : null,
|
|
||||||
'date' => $aPhoto['date']->format('Y-m-d H:i:s'),
|
|
||||||
'ordering' => $aPhoto['ordering']
|
|
||||||
];
|
|
||||||
|
|
||||||
if (isset($aPhoto['exif']['Make'])) {
|
|
||||||
$aMatter['exif'] = [
|
|
||||||
'make' => $aPhoto['exif']['Make'],
|
|
||||||
'model' => $aPhoto['exif']['Model'],
|
|
||||||
'aperture' => $aPhoto['exif']['COMPUTED']['ApertureFNumber'],
|
|
||||||
'exposure' => $aPhoto['exif']['ExposureTime'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($aPhotos[$i - 1])) {
|
|
||||||
$aMatter['previous'] = '/gallery/' . $sGallery . '/' . $aPhotos[$i - 1]['id'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($aPhotos[$i + 1])) {
|
|
||||||
$aMatter['next'] = '/gallery/' . $sGallery . '/' . $aPhotos[$i + 1]['id'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($aPhoto['latitude'])) {
|
|
||||||
$aMatter['location'] = [
|
|
||||||
'latitude' => $aPhoto['latitude'],
|
|
||||||
'longitude' => $aPhoto['longitude'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($aPhoto['sizes']) {
|
|
||||||
$aMatter['sizes'] = $aPhoto['sizes'];
|
|
||||||
}
|
|
||||||
|
|
||||||
ksort_recursive($aMatter);
|
|
||||||
uasort(
|
|
||||||
$aMatter['sizes'],
|
|
||||||
function ($aA, $aB) {
|
|
||||||
$iSurfaceA = $aA['width'] * $aA['height'];
|
|
||||||
$iSurfaceB = $aB['width'] * $aB['height'];
|
|
||||||
return $iSurfaceA == $iSurfaceB
|
|
||||||
? 0
|
|
||||||
: (($iSurfaceA > $iSurfaceB)? -1 : 1);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Write Markdown file
|
|
||||||
file_put_contents(
|
|
||||||
$sRenderPath . '/' . $aPhoto['id'] . '.md',
|
|
||||||
'---' . "\n" . Yaml::dump($aMatter, 4, 2) . '---' . "\n" . ((!empty($aPhoto['comment'])) ? ($aPhoto['comment'] . "\n") : '')
|
|
||||||
);
|
|
||||||
|
|
||||||
$oOutput->writeln(' done');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
$oConsole->run(new ArgvInput(array_merge([$_SERVER['argv'][0], 'run' ], array_slice($_SERVER['argv'], 1))));
|
|
||||||
|
|
||||||
function ksort_recursive(&$aArray, $mSortFlags = SORT_REGULAR) {
|
|
||||||
if (!is_array($aArray)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
foreach ($aArray as &$aSubarray) {
|
|
||||||
ksort_recursive($aSubarray, $mSortFlags);
|
|
||||||
}
|
|
||||||
ksort($aArray, $mSortFlags);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function coordinateToDegrees($aCoordinate, $sHemisphere) {
|
|
||||||
$aCoordinate = array_map('fractionToFloat', $aCoordinate);
|
|
||||||
$aDegrees = array_map(function ($a, $b) {
|
|
||||||
return $a / $b;
|
|
||||||
}, $aCoordinate, array(1, 60, 3600));
|
|
||||||
$iFlip = ($sHemisphere == 'W' or $sHemisphere == 'S') ? -1 : 1;
|
|
||||||
return $iFlip * array_sum($aDegrees);
|
|
||||||
}
|
|
||||||
|
|
||||||
function fractionToFloat($sFraction) {
|
|
||||||
$aParts = explode('/', $sFraction);
|
|
||||||
$iParts = count($aParts);
|
|
||||||
return $iParts
|
|
||||||
? ($iParts > 1
|
|
||||||
? floatval($aParts[0]) / floatval($aParts[1])
|
|
||||||
: $aParts[0])
|
|
||||||
: 0;
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
title: Galleries
|
title: Galleries
|
||||||
layout: default
|
layout: default
|
||||||
---
|
---
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
{% loopdir path:"gallery" match:"*/index.html" sort:"-date" %}
|
{% loopdir path:"gallery" match:"*/index.html" sort:"-date" %}
|
||||||
|
{{ itme }}
|
||||||
<div style="display: inline-block; height: 274px; margin: 8px 10px 0 0; text-align: center; width: 200px;">
|
<div style="display: inline-block; height: 274px; margin: 8px 10px 0 0; text-align: center; width: 200px;">
|
||||||
<a href="/{{ site.baseurl }}/{{ item.path|replace:'/index.html','/' }}" style="text-decoration:none;">
|
<a href="/{{ site.baseurl }}/{{ item.path|replace:'/index.html','/' }}" style="text-decoration:none;">
|
||||||
<img alt="{{ item.title }}" height="200" src="{{ site.asset_prefix }}/{{ item.path|replace:'/index.html','' }}/{{ item.highlight_photo }}~200x200.jpg" title="{{ item.title }}" width="200" style="border-radius:4px;" />
|
<img alt="{{ item.title }}" height="200" src="{{ site.asset_prefix }}/{{ item.path|replace:'/index.html','' }}/{{ item.highlight_photo }}~200x200.jpg" title="{{ item.title }}" width="200" style="border-radius:4px;" />
|
||||||
|
|||||||
Reference in New Issue
Block a user