From 6e9abff2e11f6dd37852889a86a3008690c265b1 Mon Sep 17 00:00:00 2001 From: Danny Berger Date: Tue, 8 Apr 2014 14:29:15 -0600 Subject: [PATCH 1/8] initial commit --- .gitignore | 3 + README.md | 5 + composer.json | 7 ++ convert.php | 235 ++++++++++++++++++++++++++++++++++++++ export-iphoto.applescript | 31 +++++ 5 files changed, 281 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 composer.json create mode 100644 convert.php create mode 100644 export-iphoto.applescript diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ca60d37 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/vendor +/composer.lock +/composer.phar diff --git a/README.md b/README.md new file mode 100644 index 0000000..689797d --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +Installation... + + $ php composer.phar install + +You might also need PHP 5.5. diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..fe66677 --- /dev/null +++ b/composer.json @@ -0,0 +1,7 @@ +{ + "require": { + "imagine/imagine": "0.5.0", + "symfony/console": "2.4.3", + "symfony/yaml": "2.4.3" + } +} diff --git a/convert.php b/convert.php new file mode 100644 index 0000000..59f4796 --- /dev/null +++ b/convert.php @@ -0,0 +1,235 @@ +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 photos', 'gallery-photo'), + 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 $input, OutputInterface $output) { + $gallery = $input->getArgument('name'); + $assetPath = $input->getArgument('assetdir') . '/' . $gallery; + $renderPath = $input->getArgument('mdowndir') . '/' . $gallery; + $layout = $input->getOption('layout'); + $exports = $input->getOption('export'); + + $stdin = stream_get_contents(STDIN); + + $imagine = new Imagine\Gd\Imagine(); + + if (!is_dir($assetPath)) { + mkdir($assetPath, 0700, true); + } + + if (!is_dir($renderPath)) { + mkdir($renderPath, 0700, true); + } + + $stdinPhotos = explode('------------', trim($stdin)); + $photos = []; + + // load data + + foreach ($stdinPhotos as $i => $photoRaw) { + $photoSplit = explode('------', trim($photoRaw), 2); + + if (empty($photoSplit[0])) { + continue; + } + + $photo = array_merge( + [ + 'ordering' => $i, + 'comment' => isset($photoSplit[1]) ? $photoSplit[1] : null, + ], + Yaml::parse($photoSplit[0]) + ); + + $photo['id'] = substr(sha1_file($photo['path']), 0, 7) . '-' . preg_replace('/(-| )+/', '-', preg_replace('/[^a-z0-9 ]/i', '-', preg_replace('/\'/', '', strtolower(preg_replace('/\p{Mn}/u', '', Normalizer::normalize($photo['title'], Normalizer::FORM_KD)))))); + + $photo['date'] = \DateTime::createFromFormat( + 'l, F j, Y \a\t g:i:s A', + $photo['date'] + ); + + $photo['exif'] = exif_read_data($photo['path']); + + $photos[] = $photo; + } + + // manipulate + + foreach ($photos as $i => $photo) { + $output->write($photo['id'] . '...'); + + // image exports + if (0 < count($exports)) { + $sourceJpg = $imagine->open($photo['path']); + $sourceSize = $sourceJpg->getSize(); + + if (isset($photo['exif']['Orientation'])) { + switch ($photo['exif']['Orientation']) { + case 2: + $sourceJpg->mirror(); + + break; + + case 3: + $sourceJpg->rotate(180); + + break; + + case 4: + $sourceJpg->rotate(180)->mirror(); + + break; + + case 5: + $sourceJpg->rotate(90)->mirror(); + + break; + + case 6: + $sourceJpg->rotate(90); + + break; + + case 7: + $sourceJpg->rotate(-90)->mirror(); + + break; + + case 8: + $sourceJpg->rotate(-90); + + break; + } + } + + foreach ($exports as $export) { + $output->write('' . $export . '...'); + + if (false !== strpos($export, 'x')) { + list($w, $h) = explode('x', $export); + + $exportImage = $sourceJpg->thumbnail( + new \Imagine\Image\Box($w, $h), + \Imagine\Image\ImageInterface::THUMBNAIL_OUTBOUND + ); + } else { + if ($sourceSize->getWidth() == max($sourceSize->getWidth(), $sourceSize->getHeight())) { + $mx = (int) $export; + $r = $mx / $sourceSize->getWidth(); + $my = $sourceSize->getHeight() * $r; + } elseif ($sourceSize->getHeight() == max($sourceSize->getWidth(), $sourceSize->getHeight())) { + $my = (int) $export; + $r = $my / $sourceSize->getHeight(); + $mx = $sourceSize->getWidth() * $r; + } + + $exportImage = $sourceJpg->thumbnail( + new \Imagine\Image\Box(ceil($mx), ceil($my)), + \Imagine\Image\ImageInterface::THUMBNAIL_INSET + ); + } + + $exportPath = $assetPath . '/' . $photo['id'] . '~' . $export . '.jpg'; + + file_put_contents( + $exportPath, + $exportImage->get('jpeg', [ 'quality' => 90 ]) + ); + + touch($exportPath, $photo['date']->getTimestamp()); + + $exportImage = null; + } + + $sourceJpg = null; + } + + $output->write('mdown...'); + + $matter = [ + 'layout' => $layout, + 'title' => $photo['title'], + 'date' => $photo['date']->format('Y-m-d H:i:s'), + 'ordering' => $photo['ordering'], + ]; + + if ($photo['exif']) { + $matter['exif'] = [ + 'make' => $photo['exif']['Make'], + 'model' => $photo['exif']['Model'], + 'aperture' => $photo['exif']['COMPUTED']['ApertureFNumber'], + 'exposure' => $photo['exif']['ExposureTime'], + ]; + } + + if (isset($photos[$i - 1])) { + $matter['previous'] = '/gallery/' . $gallery . '/' . $photos[$i - 1]['id']; + } + + if (isset($photos[$i + 1])) { + $matter['next'] = '/gallery/' . $gallery . '/' . $photos[$i + 1]['id']; + } + + if ($photo['latitude']) { + $matter['location'] = [ + 'latitude' => $photo['latitude'], + 'longitude' => $photo['longitude'], + ]; + } + + ksort_recursive($matter); + + file_put_contents( + $renderPath . '/' . $photo['id'] . '.md', + '---' . "\n" . Yaml::dump($matter, 4, 2) . '---' . "\n" . ((!empty($photo['comment'])) ? ($photo['comment'] . "\n") : '') + ); + + $output->writeln('done'); + } + } + ) + ; + +$console->run(new ArgvInput(array_merge([ $_SERVER['argv'][0], 'run' ], array_slice($_SERVER['argv'], 1)))); + +function ksort_recursive (&$array, $sort_flags = SORT_REGULAR) { + if (!is_array($array)) { + return false; + } + + foreach ($array as &$subarray) { + ksort_recursive($subarray, $sort_flags); + } + + ksort($array, $sort_flags); + + return true; +} diff --git a/export-iphoto.applescript b/export-iphoto.applescript new file mode 100644 index 0000000..14cc6b7 --- /dev/null +++ b/export-iphoto.applescript @@ -0,0 +1,31 @@ +on run argv + tell application "iPhoto" + set vAlbum to first item of (get every album whose name is (item 1 of argv)) + set vPhotos to get every photo in vAlbum + + set output to "" + + repeat with vPhoto in vPhotos + set output to output & Â + "altitude: " & altitude of vPhoto & " +" & Â + "latitude: " & latitude of vPhoto & " +" & Â + "longitude: " & longitude of vPhoto & " +" & Â + "name: " & name of vPhoto & " +" & Â + "date: " & date of vPhoto & " +" & Â + "path: " & original path of vPhoto & " +" & Â + "title: " & title of vPhoto & " +------ +" & comment of vPhoto & " +------------ +" + end repeat + + return output + end tell +end run \ No newline at end of file From 1666c9109efc4333341023e5349da556c6c889f7 Mon Sep 17 00:00:00 2001 From: Danny Berger Date: Sun, 28 Sep 2014 12:08:58 -0600 Subject: [PATCH 2/8] Embed exported sizes into metadata; add support for fixed height/width exports --- convert.php | 56 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/convert.php b/convert.php index 59f4796..45d77a1 100644 --- a/convert.php +++ b/convert.php @@ -84,12 +84,13 @@ The export option will accept values like: // manipulate foreach ($photos as $i => $photo) { - $output->write($photo['id'] . '...'); + $output->write('' . $photo['id'] . ''); + + $photo['sizes'] = []; // image exports if (0 < count($exports)) { $sourceJpg = $imagine->open($photo['path']); - $sourceSize = $sourceJpg->getSize(); if (isset($photo['exif']['Orientation'])) { switch ($photo['exif']['Orientation']) { @@ -130,8 +131,12 @@ The export option will accept values like: } } + $sourceSize = $sourceJpg->getSize(); + + $output->write('[' . $sourceSize->getWidth() . 'x' . $sourceSize->getHeight() . ']...'); + foreach ($exports as $export) { - $output->write('' . $export . '...'); + $output->write('' . $export . ''); if (false !== strpos($export, 'x')) { list($w, $h) = explode('x', $export); @@ -141,14 +146,18 @@ The export option will accept values like: \Imagine\Image\ImageInterface::THUMBNAIL_OUTBOUND ); } else { - if ($sourceSize->getWidth() == max($sourceSize->getWidth(), $sourceSize->getHeight())) { + if ('w' == substr($export, -1)) { $mx = (int) $export; - $r = $mx / $sourceSize->getWidth(); - $my = $sourceSize->getHeight() * $r; + $my = ($mx * $sourceSize->getHeight() ) / $sourceSize->getWidth(); + } elseif ('h' == substr($export, -1)) { + $my = (int) $export; + $mx = ($my * $sourceSize->getWidth() ) / $sourceSize->getHeight(); + } elseif ($sourceSize->getWidth() == max($sourceSize->getWidth(), $sourceSize->getHeight())) { + $mx = (int) $export; + $my = ($mx * $sourceSize->getHeight()) / $sourceSize->getWidth(); } elseif ($sourceSize->getHeight() == max($sourceSize->getWidth(), $sourceSize->getHeight())) { $my = (int) $export; - $r = $my / $sourceSize->getHeight(); - $mx = $sourceSize->getWidth() * $r; + $mx = ($my * $sourceSize->getWidth()) / $sourceSize->getHeight(); } $exportImage = $sourceJpg->thumbnail( @@ -157,6 +166,15 @@ The export option will accept values like: ); } + $exportSize = $exportImage->getSize(); + + $photo['sizes'][$export] = [ + 'width' => $exportSize->getWidth(), + 'height' => $exportSize->getHeight(), + ]; + + $output->write('[' . $exportSize->getWidth() . 'x' . $exportSize->getHeight() . ']'); + $exportPath = $assetPath . '/' . $photo['id'] . '~' . $export . '.jpg'; file_put_contents( @@ -167,12 +185,14 @@ The export option will accept values like: touch($exportPath, $photo['date']->getTimestamp()); $exportImage = null; + + $output->write('...'); } $sourceJpg = null; } - $output->write('mdown...'); + $output->write('markdown...'); $matter = [ 'layout' => $layout, @@ -205,8 +225,26 @@ The export option will accept values like: ]; } + if ($photo['sizes']) { + $matter['sizes'] = $photo['sizes']; + } + ksort_recursive($matter); + uasort( + $matter['sizes'], + function ($a, $b) { + $aa = $a['width'] * $a['height']; + $bb = $b['width'] * $b['height']; + + if ($aa == $bb) { + return 0; + } + + return ($aa > $bb) ? -1 : 1; + } + ); + file_put_contents( $renderPath . '/' . $photo['id'] . '.md', '---' . "\n" . Yaml::dump($matter, 4, 2) . '---' . "\n" . ((!empty($photo['comment'])) ? ($photo['comment'] . "\n") : '') From 66adb1a1a4e5b8f5f3033fdcf38fdc01de96c99c Mon Sep 17 00:00:00 2001 From: Rik Veenboer Date: Sat, 19 Dec 2015 22:55:15 +0000 Subject: [PATCH 3/8] Clean convert.php, use Hungarian notation --- convert.php | 294 +++++++++++++++++++++++----------------------------- 1 file changed, 128 insertions(+), 166 deletions(-) diff --git a/convert.php b/convert.php index 45d77a1..84e445a 100644 --- a/convert.php +++ b/convert.php @@ -1,5 +1,4 @@ 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 photos', 'gallery-photo'), - 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'), - ] - ) + ->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 photos', 'gallery-photo'), + 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 -') + 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 $input, OutputInterface $output) { - $gallery = $input->getArgument('name'); - $assetPath = $input->getArgument('assetdir') . '/' . $gallery; - $renderPath = $input->getArgument('mdowndir') . '/' . $gallery; - $layout = $input->getOption('layout'); - $exports = $input->getOption('export'); + function (InputInterface $oInput, OutputInterface $oOutput) { + $sGallery = $oInput->getArgument('name'); + $sAssetPath = $oInput->getArgument('assetdir') . '/' . $sGallery; + $sRenderPath = $oInput->getArgument('mdowndir') . '/' . $sGallery; + $sLayout = $oInput->getOption('layout'); + $sExports = $oInput->getOption('export'); - $stdin = stream_get_contents(STDIN); + $sStdin = stream_get_contents(STDIN); - $imagine = new Imagine\Gd\Imagine(); + $oImagine = new Imagine\Gd\Imagine(); - if (!is_dir($assetPath)) { - mkdir($assetPath, 0700, true); + if (!is_dir($sAssetPath)) { + mkdir($sAssetPath, 0700, true); } - if (!is_dir($renderPath)) { - mkdir($renderPath, 0700, true); + if (!is_dir($sRenderPath)) { + mkdir($sRenderPath, 0700, true); } - $stdinPhotos = explode('------------', trim($stdin)); - $photos = []; + $sStdinPhotos = explode('------------', trim($sStdin)); + $aPhotos = []; // load data + foreach ($sStdinPhotos as $i => $aPhotoRaw) { + $aPhotoSplit = explode('------', trim($aPhotoRaw), 2); - foreach ($stdinPhotos as $i => $photoRaw) { - $photoSplit = explode('------', trim($photoRaw), 2); - - if (empty($photoSplit[0])) { + if (empty($aPhotoSplit[0])) { continue; } - $photo = array_merge( - [ - 'ordering' => $i, - 'comment' => isset($photoSplit[1]) ? $photoSplit[1] : null, - ], - Yaml::parse($photoSplit[0]) - ); + $aPhoto = array_merge([ + 'ordering' => $i, + 'comment' => isset($aPhotoSplit[1]) ? $aPhotoSplit[1] : null, + ], Yaml::parse($aPhotoSplit[0])); - $photo['id'] = substr(sha1_file($photo['path']), 0, 7) . '-' . preg_replace('/(-| )+/', '-', preg_replace('/[^a-z0-9 ]/i', '-', preg_replace('/\'/', '', strtolower(preg_replace('/\p{Mn}/u', '', Normalizer::normalize($photo['title'], Normalizer::FORM_KD)))))); - - $photo['date'] = \DateTime::createFromFormat( + $aPhoto['id'] = substr(sha1_file($aPhoto['path']), 0, 7) . '-' . preg_replace('/(-| )+/', '-', preg_replace('/[^a-z0-9 ]/i', '-', preg_replace('/\'/', '', strtolower(preg_replace('/\p{Mn}/u', '', Normalizer::normalize($aPhoto['title'], Normalizer::FORM_KD)))))); + $aPhoto['date'] = \DateTime::createFromFormat( 'l, F j, Y \a\t g:i:s A', - $photo['date'] + $aPhoto['date'] ); - $photo['exif'] = exif_read_data($photo['path']); - - $photos[] = $photo; + $aPhoto['exif'] = exif_read_data($aPhoto['path']); + $aPhotos[] = $aPhoto; } // manipulate + foreach ($aPhotos as $i => $aPhoto) { + $oOutput->write('' . $aPhoto['id'] . ''); - foreach ($photos as $i => $photo) { - $output->write('' . $photo['id'] . ''); - - $photo['sizes'] = []; + $aPhoto['sizes'] = []; // image exports - if (0 < count($exports)) { - $sourceJpg = $imagine->open($photo['path']); - - if (isset($photo['exif']['Orientation'])) { - switch ($photo['exif']['Orientation']) { + if (0 < count($sExports)) { + $oSourceJpg = $oImagine->open($aPhoto['path']); + if (isset($aPhoto['exif']['Orientation'])) { + switch ($aPhoto['exif']['Orientation']) { case 2: - $sourceJpg->mirror(); - + $oSourceJpg->mirror(); break; - case 3: - $sourceJpg->rotate(180); - + $oSourceJpg->rotate(180); break; - case 4: - $sourceJpg->rotate(180)->mirror(); - + $oSourceJpg->rotate(180)->mirror(); break; - case 5: - $sourceJpg->rotate(90)->mirror(); - + $oSourceJpg->rotate(90)->mirror(); break; - case 6: - $sourceJpg->rotate(90); - + $oSourceJpg->rotate(90); break; - case 7: - $sourceJpg->rotate(-90)->mirror(); - + $oSourceJpg->rotate(-90)->mirror(); break; - case 8: - $sourceJpg->rotate(-90); - + $oSourceJpg->rotate(-90); break; } } - $sourceSize = $sourceJpg->getSize(); + $oSourceSize = $oSourceJpg->getSize(); + $oOutput->write('[' . $oSourceSize->getWidth() . 'x' . $oSourceSize->getHeight() . ']...'); - $output->write('[' . $sourceSize->getWidth() . 'x' . $sourceSize->getHeight() . ']...'); + foreach ($sExports as $sExport) { + $oOutput->write('' . $sExport . ''); - foreach ($exports as $export) { - $output->write('' . $export . ''); - - if (false !== strpos($export, 'x')) { - list($w, $h) = explode('x', $export); - - $exportImage = $sourceJpg->thumbnail( - new \Imagine\Image\Box($w, $h), + 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($export, -1)) { - $mx = (int) $export; - $my = ($mx * $sourceSize->getHeight() ) / $sourceSize->getWidth(); - } elseif ('h' == substr($export, -1)) { - $my = (int) $export; - $mx = ($my * $sourceSize->getWidth() ) / $sourceSize->getHeight(); - } elseif ($sourceSize->getWidth() == max($sourceSize->getWidth(), $sourceSize->getHeight())) { - $mx = (int) $export; - $my = ($mx * $sourceSize->getHeight()) / $sourceSize->getWidth(); - } elseif ($sourceSize->getHeight() == max($sourceSize->getWidth(), $sourceSize->getHeight())) { - $my = (int) $export; - $mx = ($my * $sourceSize->getWidth()) / $sourceSize->getHeight(); - } - - $exportImage = $sourceJpg->thumbnail( - new \Imagine\Image\Box(ceil($mx), ceil($my)), + 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 ); } - $exportSize = $exportImage->getSize(); + $sExportsize = $sExportImage->getSize(); - $photo['sizes'][$export] = [ - 'width' => $exportSize->getWidth(), - 'height' => $exportSize->getHeight(), + $aPhoto['sizes'][$sExport] = [ + 'width' => $sExportsize->getWidth(), + 'height' => $sExportsize->getHeight(), ]; - $output->write('[' . $exportSize->getWidth() . 'x' . $exportSize->getHeight() . ']'); + $oOutput->write('[' . $sExportsize->getWidth() . 'x' . $sExportsize->getHeight() . ']'); - $exportPath = $assetPath . '/' . $photo['id'] . '~' . $export . '.jpg'; + $sExportPath = $sAssetPath . '/' . $aPhoto['id'] . '~' . $sExport . '.jpg'; file_put_contents( - $exportPath, - $exportImage->get('jpeg', [ 'quality' => 90 ]) + $sExportPath, + $sExportImage->get('jpeg', ['quality' => 90]) ); - touch($exportPath, $photo['date']->getTimestamp()); - - $exportImage = null; - - $output->write('...'); + touch($sExportPath, $aPhoto['date']->getTimestamp()); + $sExportImage = null; + $oOutput->write('...'); } - - $sourceJpg = null; + $oSourceJpg = null; } - $output->write('markdown...'); + $oOutput->write('markdown...'); - $matter = [ - 'layout' => $layout, - 'title' => $photo['title'], - 'date' => $photo['date']->format('Y-m-d H:i:s'), - 'ordering' => $photo['ordering'], + $aMatter = [ + 'layout' => $sLayout, + 'title' => $aPhoto['title'], + 'date' => $aPhoto['date']->format('Y-m-d H:i:s'), + 'ordering' => $aPhoto['ordering'] ]; - if ($photo['exif']) { - $matter['exif'] = [ - 'make' => $photo['exif']['Make'], - 'model' => $photo['exif']['Model'], - 'aperture' => $photo['exif']['COMPUTED']['ApertureFNumber'], - 'exposure' => $photo['exif']['ExposureTime'], + if ($aPhoto['exif']) { + $aMatter['exif'] = [ + 'make' => $aPhoto['exif']['Make'], + 'model' => $aPhoto['exif']['Model'], + 'aperture' => $aPhoto['exif']['COMPUTED']['ApertureFNumber'], + 'exposure' => $aPhoto['exif']['ExposureTime'], ]; } - if (isset($photos[$i - 1])) { - $matter['previous'] = '/gallery/' . $gallery . '/' . $photos[$i - 1]['id']; + if (isset($aPhotos[$i - 1])) { + $aMatter['previous'] = '/gallery/' . $sGallery . '/' . $aPhotos[$i - 1]['id']; } - if (isset($photos[$i + 1])) { - $matter['next'] = '/gallery/' . $gallery . '/' . $photos[$i + 1]['id']; + if (isset($aPhotos[$i + 1])) { + $aMatter['next'] = '/gallery/' . $sGallery . '/' . $aPhotos[$i + 1]['id']; } - if ($photo['latitude']) { - $matter['location'] = [ - 'latitude' => $photo['latitude'], - 'longitude' => $photo['longitude'], + if ($aPhoto['latitude']) { + $aMatter['location'] = [ + 'latitude' => $aPhoto['latitude'], + 'longitude' => $aPhoto['longitude'], ]; } - if ($photo['sizes']) { - $matter['sizes'] = $photo['sizes']; + if ($aPhoto['sizes']) { + $aMatter['sizes'] = $aPhoto['sizes']; } - ksort_recursive($matter); + ksort_recursive($aMatter); uasort( - $matter['sizes'], - function ($a, $b) { - $aa = $a['width'] * $a['height']; - $bb = $b['width'] * $b['height']; - - if ($aa == $bb) { - return 0; - } - - return ($aa > $bb) ? -1 : 1; + $aMatter['sizes'], + function ($aA, $aB) { + $iSurfaceA = $aA['width'] * $aA['height']; + $iSurfaceB = $aB['width'] * $aB['height']; + return $iSurfaceA == $iSurfaceB + ? 0 + : (($aa > $bb)? -1 : 1); } ); file_put_contents( - $renderPath . '/' . $photo['id'] . '.md', - '---' . "\n" . Yaml::dump($matter, 4, 2) . '---' . "\n" . ((!empty($photo['comment'])) ? ($photo['comment'] . "\n") : '') + $sRenderPath . '/' . $aPhoto['id'] . '.md', + '---' . "\n" . Yaml::dump($aMatter, 4, 2) . '---' . "\n" . ((!empty($aPhoto['comment'])) ? ($aPhoto['comment'] . "\n") : '') ); - $output->writeln('done'); + $oOutput->writeln('done'); } - } - ) - ; + }); -$console->run(new ArgvInput(array_merge([ $_SERVER['argv'][0], 'run' ], array_slice($_SERVER['argv'], 1)))); +$oConsole->run(new ArgvInput(array_merge([$_SERVER['argv'][0], 'run' ], array_slice($_SERVER['argv'], 1)))); -function ksort_recursive (&$array, $sort_flags = SORT_REGULAR) { - if (!is_array($array)) { +function ksort_recursive(&$aArray, $mSortFlags = SORT_REGULAR) { + if (!is_array($aArray)) { return false; } - - foreach ($array as &$subarray) { - ksort_recursive($subarray, $sort_flags); + foreach ($aArray as &$aSubarray) { + ksort_recursive($aSubarray, $mSortFlags); } - - ksort($array, $sort_flags); - + ksort($aArray, $mSortFlags); return true; } From c4dac0c76cf9b64bed17c8c66ca47d13df772cb0 Mon Sep 17 00:00:00 2001 From: Rik Veenboer Date: Sun, 20 Dec 2015 00:58:30 +0000 Subject: [PATCH 4/8] Read files directly from gallery or dedicated yaml file --- convert.php | 160 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 115 insertions(+), 45 deletions(-) diff --git a/convert.php b/convert.php index 84e445a..2d7678c 100644 --- a/convert.php +++ b/convert.php @@ -9,13 +9,43 @@ 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'); + + +// $sTest = <<register('run') - ->setDefinition([ + ->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 photos', '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', 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'), @@ -28,57 +58,79 @@ $oConsole ') ->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'); - $sStdin = stream_get_contents(STDIN); - $oImagine = new Imagine\Gd\Imagine(); + // Initialize directories if (!is_dir($sAssetPath)) { mkdir($sAssetPath, 0700, true); } - if (!is_dir($sRenderPath)) { mkdir($sRenderPath, 0700, true); } - $sStdinPhotos = explode('------------', trim($sStdin)); + 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 + ]; - // load data - foreach ($sStdinPhotos as $i => $aPhotoRaw) { - $aPhotoSplit = explode('------', trim($aPhotoRaw), 2); - - if (empty($aPhotoSplit[0])) { - continue; + // 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)))))); } - $aPhoto = array_merge([ - 'ordering' => $i, - 'comment' => isset($aPhotoSplit[1]) ? $aPhotoSplit[1] : null, - ], Yaml::parse($aPhotoSplit[0])); - - $aPhoto['id'] = substr(sha1_file($aPhoto['path']), 0, 7) . '-' . preg_replace('/(-| )+/', '-', preg_replace('/[^a-z0-9 ]/i', '-', preg_replace('/\'/', '', strtolower(preg_replace('/\p{Mn}/u', '', Normalizer::normalize($aPhoto['title'], Normalizer::FORM_KD)))))); - $aPhoto['date'] = \DateTime::createFromFormat( - 'l, F j, Y \a\t g:i:s A', - $aPhoto['date'] - ); - + // 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 + // Manipulate foreach ($aPhotos as $i => $aPhoto) { $oOutput->write('' . $aPhoto['id'] . ''); - $aPhoto['sizes'] = []; - // image exports + // Image exports if (0 < count($sExports)) { $oSourceJpg = $oImagine->open($aPhoto['path']); if (isset($aPhoto['exif']['Orientation'])) { @@ -108,10 +160,10 @@ $oConsole } $oSourceSize = $oSourceJpg->getSize(); - $oOutput->write('[' . $oSourceSize->getWidth() . 'x' . $oSourceSize->getHeight() . ']...'); + $oOutput->writeln(' [' . $oSourceSize->getWidth() . 'x' . $oSourceSize->getHeight() . ']...'); foreach ($sExports as $sExport) { - $oOutput->write('' . $sExport . ''); + $oOutput->write(' ' . $sExport . ''); if (false !== strpos($sExport, 'x')) { list($iW, $iH) = explode('x', $sExport); @@ -146,10 +198,10 @@ $oConsole 'height' => $sExportsize->getHeight(), ]; - $oOutput->write('[' . $sExportsize->getWidth() . 'x' . $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]) @@ -157,21 +209,19 @@ $oConsole touch($sExportPath, $aPhoto['date']->getTimestamp()); $sExportImage = null; - $oOutput->write('...'); } $oSourceJpg = null; } - $oOutput->write('markdown...'); - + $oOutput->write(' markdown'); $aMatter = [ 'layout' => $sLayout, - 'title' => $aPhoto['title'], + 'title' => isset($aPhoto['title']) ? $aPhoto['title'] : null, 'date' => $aPhoto['date']->format('Y-m-d H:i:s'), 'ordering' => $aPhoto['ordering'] ]; - if ($aPhoto['exif']) { + if (isset($aPhoto['exif']['Make'])) { $aMatter['exif'] = [ 'make' => $aPhoto['exif']['Make'], 'model' => $aPhoto['exif']['Model'], @@ -188,7 +238,7 @@ $oConsole $aMatter['next'] = '/gallery/' . $sGallery . '/' . $aPhotos[$i + 1]['id']; } - if ($aPhoto['latitude']) { + if (isset($aPhoto['latitude'])) { $aMatter['location'] = [ 'latitude' => $aPhoto['latitude'], 'longitude' => $aPhoto['longitude'], @@ -200,7 +250,6 @@ $oConsole } ksort_recursive($aMatter); - uasort( $aMatter['sizes'], function ($aA, $aB) { @@ -208,18 +257,20 @@ $oConsole $iSurfaceB = $aB['width'] * $aB['height']; return $iSurfaceA == $iSurfaceB ? 0 - : (($aa > $bb)? -1 : 1); + : (($iSurfaceA > $iSurfaceB)? -1 : 1); } ); - file_put_contents( - $sRenderPath . '/' . $aPhoto['id'] . '.md', - '---' . "\n" . Yaml::dump($aMatter, 4, 2) . '---' . "\n" . ((!empty($aPhoto['comment'])) ? ($aPhoto['comment'] . "\n") : '') - ); + // 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'); - } - }); + $oOutput->writeln(' done'); + } + } +); $oConsole->run(new ArgvInput(array_merge([$_SERVER['argv'][0], 'run' ], array_slice($_SERVER['argv'], 1)))); @@ -233,3 +284,22 @@ function ksort_recursive(&$aArray, $mSortFlags = SORT_REGULAR) { 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; +} \ No newline at end of file From 6b2cd1f8c15cbb4c1a3642e83f3d98ae3f6abb83 Mon Sep 17 00:00:00 2001 From: Rik Veenboer Date: Sun, 20 Dec 2015 00:59:45 +0000 Subject: [PATCH 5/8] Add directories for generated files to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index ca60d37..1a0a9a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /vendor /composer.lock /composer.phar +/asset +/gallery From 9c56abbb83145488974f5e92f84957c1eee5a38e Mon Sep 17 00:00:00 2001 From: Rik Veenboer Date: Sun, 20 Dec 2015 01:00:28 +0000 Subject: [PATCH 6/8] Add tool for copying GPS data from JPG to DNG files --- composer.json | 3 +- gps-jpg2raw.php | 101 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 gps-jpg2raw.php diff --git a/composer.json b/composer.json index fe66677..7057684 100644 --- a/composer.json +++ b/composer.json @@ -2,6 +2,7 @@ "require": { "imagine/imagine": "0.5.0", "symfony/console": "2.4.3", - "symfony/yaml": "2.4.3" + "symfony/yaml": "2.4.3", + "phpexiftool/phpexiftool": "0.4.1" } } diff --git a/gps-jpg2raw.php b/gps-jpg2raw.php new file mode 100644 index 0000000..ee5f79d --- /dev/null +++ b/gps-jpg2raw.php @@ -0,0 +1,101 @@ + $sFile) { + printf("Processing %s (%d/%d)...\n", $sFile, $i, $iFiles); + + // Regenerate JPG and DNG file paths + $sJpgPath = sprintf('%s%s%s.jpg', $sDir, DIRECTORY_SEPARATOR, $sFile); + $sDngPath = sprintf('%s%s%s.dng', $sDir, DIRECTORY_SEPARATOR, $sFile); + + // Read from JPG file + printf(" > Reading from JPG file.\n"); + $oFileEntity = $oReader->files($sJpgPath)->first(); + $oReader->reset(); + + // Extract desired GPS tags + $aData = $aMetadataBag = array(); + $aKeys = array( + 'GPS:GPSAltitude' => 'altitude', + 'GPS:GPSLongitude' => 'longitude', + 'GPS:GPSLatitude' => 'latitude', + 'GPS:GPSImgDirection' => 'direction'); + + // Loop over all metadata + printf(" > Parsing metadata.\n"); + foreach ($oFileEntity as $oMetaData) { + $oTag = $oMetaData->getTag(); + $sTag = $oTag->getTagname(); + $oValue = $oMetaData->getValue(); + + // Store desired tags + if (array_key_exists($sTag, $aKeys)) { + $aData[$aKeys[$sTag]] = $oValue; + } + + // Store all GPS tags in metadata bag + if (strpos($sTag, 'GPS:') !== false) { + $aMetadataBag[] = $oMetaData; + } + } + + // Check presence of GPS data + if (empty($aData)) { + printf(" > No GPS data present!\n"); + continue; + } + + // Convert values to floats + $aData = array_map(function ($sValue) { + return floatval($sValue->asString()); + }, $aData); + + // Write to DNG file + printf(" > Writing to DNG file.\n"); + $oWriter->write($sDngPath, new MetadataBag($aMetadataBag)); +} \ No newline at end of file From e54f6183b394ca7d2cae6a4925836deaece53dcf Mon Sep 17 00:00:00 2001 From: Rik Veenboer Date: Sun, 20 Dec 2015 01:01:03 +0000 Subject: [PATCH 7/8] Delete obsolete AppleScript --- export-iphoto.applescript | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 export-iphoto.applescript diff --git a/export-iphoto.applescript b/export-iphoto.applescript deleted file mode 100644 index 14cc6b7..0000000 --- a/export-iphoto.applescript +++ /dev/null @@ -1,31 +0,0 @@ -on run argv - tell application "iPhoto" - set vAlbum to first item of (get every album whose name is (item 1 of argv)) - set vPhotos to get every photo in vAlbum - - set output to "" - - repeat with vPhoto in vPhotos - set output to output & Â - "altitude: " & altitude of vPhoto & " -" & Â - "latitude: " & latitude of vPhoto & " -" & Â - "longitude: " & longitude of vPhoto & " -" & Â - "name: " & name of vPhoto & " -" & Â - "date: " & date of vPhoto & " -" & Â - "path: " & original path of vPhoto & " -" & Â - "title: " & title of vPhoto & " ------- -" & comment of vPhoto & " ------------- -" - end repeat - - return output - end tell -end run \ No newline at end of file From 0f3ff1fafca555c050a6723be94c67cc46639a09 Mon Sep 17 00:00:00 2001 From: Rik Veenboer Date: Sun, 20 Dec 2015 13:57:22 +0000 Subject: [PATCH 8/8] Delete commented test code --- convert.php | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/convert.php b/convert.php index 2d7678c..9bc31f4 100644 --- a/convert.php +++ b/convert.php @@ -23,21 +23,6 @@ function writeMetaYaml($sDir) { } // writeMetaYaml('C:\Users\Rik\Downloads\Blog\jekyll-gallery\in'); - -// $sTest = <<