├── .gitignore ├── screenshots ├── remove.png ├── export-1.png ├── manual-add-1.png ├── manual-add-2.png └── manual-add-3.png ├── dayone.lrdevplugin ├── Info.lua ├── ServiceProvider.lua ├── uuid4.lua ├── ExportDialogSections.lua └── ExportTask.lua ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Lightroom\ SDK\ 3.0/* 3 | test/ 4 | -------------------------------------------------------------------------------- /screenshots/remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipbl/Day-One-Lightroom-Plugin/HEAD/screenshots/remove.png -------------------------------------------------------------------------------- /screenshots/export-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipbl/Day-One-Lightroom-Plugin/HEAD/screenshots/export-1.png -------------------------------------------------------------------------------- /screenshots/manual-add-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipbl/Day-One-Lightroom-Plugin/HEAD/screenshots/manual-add-1.png -------------------------------------------------------------------------------- /screenshots/manual-add-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipbl/Day-One-Lightroom-Plugin/HEAD/screenshots/manual-add-2.png -------------------------------------------------------------------------------- /screenshots/manual-add-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philipbl/Day-One-Lightroom-Plugin/HEAD/screenshots/manual-add-3.png -------------------------------------------------------------------------------- /dayone.lrdevplugin/Info.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2013, Philip Lundrigan 2 | -- All rights reserved. 3 | -- BSD License 4 | 5 | return { 6 | 7 | LrSdkVersion = 3.0, 8 | LrToolkitIdentifier = 'com.philiplundrigan.dayone_export', 9 | 10 | LrPluginName = LOC "$$$/DayOneExport/PluginName=Day One Exporter", 11 | LrPluginInfoUrl = 'https://github.com/philipbl/Day-One-Lightroom-Plugin', 12 | 13 | LrExportServiceProvider = { 14 | title = "Day One", 15 | file = 'ServiceProvider.lua', 16 | }, 17 | 18 | VERSION = { major=1, minor=2, revision=1}, 19 | 20 | } 21 | -------------------------------------------------------------------------------- /dayone.lrdevplugin/ServiceProvider.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2013, Philip Lundrigan 2 | -- All rights reserved. 3 | -- BSD License 4 | 5 | require "ExportDialogSections" 6 | require "ExportTask" 7 | 8 | local LrPathUtils = import 'LrPathUtils' 9 | 10 | return { 11 | hideSections = { 'exportLocation', 'fileNaming' }, 12 | allowFileFormats = { 'JPEG' }, 13 | 14 | exportPresetFields = { 15 | { key = 'use_time', default = true }, 16 | { key = 'use_location', default = true }, 17 | { key = 'star', default = false }, 18 | { key = 'use_keywords', default = false }, 19 | { key = 'use_specific_tags', default = false }, 20 | { key = 'tags', default = "" }, 21 | { key = 'use_activity', default = false }, 22 | { key = 'activity', default = "" }, 23 | { key = 'journal_type', default = 'Dropbox' }, 24 | { key = 'custom', default = false }, 25 | { key = 'custom_path', default = '' }, 26 | { key = 'path', default = dropboxPath }, 27 | }, 28 | 29 | sectionsForTopOfDialog = ExportDialogSections.sectionsForTopOfDialog, 30 | 31 | processRenderedPhotos = ExportTask.processRenderedPhotos, 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Philip Lundrigan 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of this program nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL PHILIP LUNDRIGAN BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /dayone.lrdevplugin/uuid4.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | The MIT License (MIT) 3 | Copyright (c) 2012 Toby Jennings 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the "Software"), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial 12 | portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 15 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | --]] 20 | 21 | local M = {} 22 | ----- 23 | math.randomseed( os.time() ) 24 | math.random() 25 | ----- 26 | local function num2bs(num) 27 | local _mod = math.fmod or math.mod 28 | local _floor = math.floor 29 | -- 30 | local result = "" 31 | if(num == 0) then return "0" end 32 | while(num > 0) do 33 | result = _mod(num,2) .. result 34 | num = _floor(num*0.5) 35 | end 36 | return result 37 | end 38 | -- 39 | local function bs2num(num) 40 | local _sub = string.sub 41 | local index, result = 0, 0 42 | if(num == "0") then return 0; end 43 | for p=#num,1,-1 do 44 | local this_val = _sub( num, p,p ) 45 | if this_val == "1" then 46 | result = result + ( 2^index ) 47 | end 48 | index=index+1 49 | end 50 | return result 51 | end 52 | -- 53 | local function padbits(num,bits) 54 | if #num == bits then return num end 55 | if #num > bits then print("too many bits") end 56 | local pad = bits - #num 57 | for i=1,pad do 58 | num = "0" .. num 59 | end 60 | return num 61 | end 62 | -- 63 | local function getUUID() 64 | local _rnd = math.random 65 | local _fmt = string.format 66 | -- 67 | _rnd() 68 | -- 69 | local time_low_a = _rnd(0, 65535) 70 | local time_low_b = _rnd(0, 65535) 71 | -- 72 | local time_mid = _rnd(0, 65535) 73 | -- 74 | local time_hi = _rnd(0, 4095 ) 75 | time_hi = padbits( num2bs(time_hi), 12 ) 76 | local time_hi_and_version = bs2num( "0100" .. time_hi ) 77 | -- 78 | local clock_seq_hi_res = _rnd(0,63) 79 | clock_seq_hi_res = padbits( num2bs(clock_seq_hi_res), 6 ) 80 | clock_seq_hi_res = "10" .. clock_seq_hi_res 81 | -- 82 | local clock_seq_low = _rnd(0,255) 83 | clock_seq_low = padbits( num2bs(clock_seq_low), 8 ) 84 | -- 85 | local clock_seq = bs2num(clock_seq_hi_res .. clock_seq_low) 86 | -- 87 | local node = {} 88 | for i=1,6 do 89 | node[i] = _rnd(0,255) 90 | end 91 | -- 92 | local guid = "" 93 | guid = guid .. padbits(_fmt("%X",time_low_a), 4) 94 | guid = guid .. padbits(_fmt("%X",time_low_b), 4) .. "-" 95 | guid = guid .. padbits(_fmt("%X",time_mid), 4) .. "-" 96 | guid = guid .. padbits(_fmt("%X",time_hi_and_version), 4) .. "-" 97 | guid = guid .. padbits(_fmt("%X",clock_seq), 4) .. "-" 98 | -- 99 | for i=1,6 do 100 | guid = guid .. padbits(_fmt("%X",node[i]), 2) 101 | end 102 | -- 103 | return guid 104 | end 105 | -- 106 | M.getUUID = getUUID 107 | return M -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Day One Lightroom Plug-in 2 | 3 | This plug-in creates an export service for Lightroom that allows you to export pictures directly to Day One or Day One 2. 4 | 5 | **Disclaimer**: The software is supplied "as is" and all use is at your own risk. Make sure to back up your journal entries! 6 | 7 | ## Day One 2 8 | 9 | The plug-in has been updated to support [Day One 2](http://dayoneapp.com/2016/01/introducing-day-one-2/). To export pictures to Day One 2, select it as your "Journal Location". There is currently no supported way of adding multiple pictures to an entry except through the apps. When this changes, I will update the plug-in to support multiple photos in one entry. 10 | 11 | ## Install 12 | 13 | First, [download][1] the project and unzip it. Next, rename `dayone.lrdevplugin` to `dayone.lrplugin`. The `.lrdevplugin` is used for when a plug-in is being developed. 14 | 15 | There are two ways to add the plug-in to Lightroom. You can either put the plug-in anywhere on your hard drive and add it manually to Lightroom, or you can put it into Lightroom's plug-in directory and it will recognize it automatically. 16 | 17 | 18 | ### Manually 19 | To add the plug-in manually, first choose a location where you want to place the plug-in and move it there. Next, inside Lightroom, select `File > Plug-in Manager...`. 20 | 21 | ![](screenshots/manual-add-1.png) 22 | 23 | Press `Add` on the bottom left of the dialog box and locate the plug-in. When you add the plug-in, it should be automatically enabled. 24 | 25 | ![](screenshots/manual-add-2.png) 26 | 27 | ![](screenshots/manual-add-3.png) 28 | 29 | 30 | ### Automatically 31 | To add the plug-in automatically, move the plug-in to the modules folder of Lightroom. The location of the modules folder is dependent on the operating system you are using. 32 | 33 | Operating System | Path 34 | -----------------|------ 35 | In Mac OS (current user) | `~/Library/Application Support/Adobe/Lightroom/Modules` 36 | In Mac OS (all users) | `/Library/Application Support/Adobe/Lightroom/Modules` 37 | In Windows XP | `C:\Documents and Users\username\Application Data\Adobe\Lightroom\Modules` 38 | In Windows 7 / Vista | `C:\Users\username\AppData\Roaming\Adobe\Lightroom\Modules` 39 | 40 | Lightroom will automatically detect the plug-in the next time you start Lightroom. 41 | 42 | 43 | Note: If you follow the instructions to add the plug-in automatically, the remove button of the Plug-in Manager will be grayed out. You must manually delete the plug-in from the modules folder of Lightroom. To do this, select Day One Exporter plug-in and press `Show in Finder` and delete `dayone.lrplugin`. 44 | 45 | ![](screenshots/remove.png) 46 | 47 | 48 | ## Usage 49 | 50 | To use this plug-in, select the picture(s) that you want to export and press the `Export` button in the Library module of Lightroom. In the export dialog box, change the `Export To:` drop down menu to `Day One`. 51 | 52 | ![](screenshots/export-1.png) 53 | 54 | ### Journal Location Options 55 | This option allows you to choose where your Day One journal is located. If you have your Day One journal in a special location, then you can select the last option, and specify where the journal is located. The Dropbox option assumes that you are using the default Dropbox location. 56 | 57 | ### Entry Settings 58 | 59 | **Use picture's time**: This will use the time that the picture was taken as the creation time for the entry. 60 | 61 | **Star Entry**: This will star all entries that are created by this export. 62 | 63 | **Use picture's keywords as tags**: This will use Lightroom's keywords as tags in the Day One entry. 64 | 65 | **Apply specific tags**: This allows you to add specific tags to all entries that are exported. This might be useful if you want to keep track of the source of pictures in your Day One journal. Each tag must be comma separated for this to work. 66 | 67 | Warning: Pictures that are exported can be extremely large and take up a lot of space in iCloud or Dropbox. You can use the normal `Image Sizing` options to scale down the picture. 68 | 69 | ## Acknowledgment 70 | Thanks to [tcjennings](https://github.com/tcjennings/LUA-RFC-4122-UUID-Generator) for the UUID4 generator. 71 | 72 | 73 | [1]: https://github.com/philipbl/Day-One-Lightroom-Plugin/archive/master.zip 74 | -------------------------------------------------------------------------------- /dayone.lrdevplugin/ExportDialogSections.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2013, Philip Lundrigan 2 | -- All rights reserved. 3 | -- BSD License 4 | 5 | ExportDialogSections = {} 6 | 7 | LrPathUtils = import 'LrPathUtils' 8 | dayOne2Path = LrPathUtils.standardizePath('~/Library/Group Containers/5U8NS4GX82.dayoneapp2/Data/Auto Import/Default Journal.dayone') 9 | icloudPath = LrPathUtils.standardizePath('~/Library/Mobile Documents/5U8NS4GX82~com~dayoneapp~dayone/Documents/Journal_dayone') 10 | dropboxPath = LrPathUtils.getStandardFilePath('home') .. LrPathUtils.standardizePath('/Dropbox/Apps/Day One/Journal.dayone') 11 | activityList = {'Stationary', 'Walking', 'Running', 'Biking', 'Eating', 'Automotive', 'Flying'} 12 | 13 | function ExportDialogSections.sectionsForTopOfDialog( viewFactory, propertyTable ) 14 | local LrDialogs = import "LrDialogs" 15 | local LrView = import "LrView" 16 | local LrPathUtils = import 'LrPathUtils' 17 | local LrFileUtils = import 'LrFileUtils' 18 | 19 | local bind = LrView.bind 20 | local share = LrView.share 21 | 22 | local function iCloudExists() 23 | return LrFileUtils.exists( icloudPath ) 24 | end 25 | 26 | return { 27 | { 28 | title = "Journal Location", 29 | synopsis = bind 'journal_type', 30 | 31 | viewFactory:row { 32 | spacing = viewFactory:control_spacing(), 33 | viewFactory:radio_button { 34 | title = 'Day One 2', 35 | value = bind 'journal_type', 36 | checked_value = 'Day One 2', 37 | enabled = iCloudExists(), 38 | action = function () 39 | propertyTable.custom = false 40 | propertyTable.journal_type = 'Day One 2' 41 | propertyTable.path = dayOne2Path 42 | end, 43 | }, 44 | }, 45 | 46 | viewFactory:row { 47 | spacing = viewFactory:control_spacing(), 48 | viewFactory:radio_button { 49 | title = 'iCloud', 50 | value = bind 'journal_type', 51 | checked_value = 'iCloud', 52 | enabled = iCloudExists(), 53 | action = function () 54 | propertyTable.custom = false 55 | propertyTable.journal_type = 'iCloud' 56 | propertyTable.path = icloudPath 57 | end, 58 | }, 59 | }, 60 | 61 | viewFactory:row { 62 | spacing = viewFactory:control_spacing(), 63 | viewFactory:radio_button { 64 | title = 'Dropbox', 65 | value = bind 'journal_type', 66 | checked_value = 'Dropbox', 67 | action = function () 68 | propertyTable.custom = false 69 | propertyTable.journal_type = 'Dropbox' 70 | propertyTable.path = dropboxPath 71 | end, 72 | }, 73 | }, 74 | 75 | viewFactory:row { 76 | spacing = viewFactory:control_spacing(), 77 | viewFactory:radio_button { 78 | title = 'Custom', 79 | value = bind 'journal_type', 80 | checked_value = 'Custom', 81 | action = function () 82 | propertyTable.custom = true 83 | propertyTable.journal_type = 'Custom' 84 | propertyTable.path = propertyTable.custom_path 85 | end, 86 | }, 87 | 88 | viewFactory:push_button { 89 | title = "Browse", 90 | enabled = bind 'custom', 91 | action = function () 92 | local location = LrDialogs.runOpenPanel({ 93 | title = "Day One Journal Location", 94 | canChooseDirectories = true, 95 | allowsMultipleSelection = false, 96 | })[1] 97 | 98 | propertyTable.custom_path = location 99 | propertyTable.path = propertyTable.custom_path 100 | end 101 | }, 102 | 103 | }, 104 | 105 | viewFactory:row { 106 | spacing = viewFactory:control_spacing(), 107 | viewFactory:static_text { 108 | title = 'Path: ', 109 | }, 110 | 111 | viewFactory:static_text { 112 | title = bind { key = 'path', object = propertyTable }, 113 | width = 500, 114 | truncation = 'head', 115 | }, 116 | }, 117 | }, 118 | 119 | { 120 | title = "Entry Settings", 121 | 122 | viewFactory:row { 123 | spacing = viewFactory:control_spacing(), 124 | viewFactory:checkbox { 125 | title = "Use picture's time", 126 | value = bind 'use_time' 127 | }, 128 | }, 129 | 130 | viewFactory:row { 131 | spacing = viewFactory:control_spacing(), 132 | viewFactory:checkbox { 133 | title = "Use picture's location", 134 | value = bind 'use_location' 135 | }, 136 | viewFactory:static_text { 137 | title = "(if GPS coordinates are present)", 138 | enabled = false, 139 | }, 140 | }, 141 | 142 | viewFactory:row { 143 | spacing = viewFactory:control_spacing(), 144 | viewFactory:checkbox { 145 | title = "Star entry", 146 | value = bind 'star' 147 | }, 148 | }, 149 | 150 | viewFactory:row { 151 | spacing = viewFactory:control_spacing(), 152 | viewFactory:checkbox { 153 | title = "Use picture's keywords as tags", 154 | value = bind 'use_keywords' 155 | }, 156 | }, 157 | 158 | viewFactory:row { 159 | spacing = viewFactory:control_spacing(), 160 | viewFactory:checkbox { 161 | title = "Apply specific tags:", 162 | value = bind 'use_specific_tags' 163 | }, 164 | viewFactory:edit_field { 165 | value = bind 'tags', 166 | enabled = bind 'use_specific_tags', 167 | immediate = true, 168 | }, 169 | viewFactory:static_text { 170 | title = "(comma separated)", 171 | enabled = false, 172 | }, 173 | }, 174 | 175 | viewFactory:row { 176 | spacing = viewFactory:control_spacing(), 177 | viewFactory:checkbox { 178 | title = "Apply motion activity:", 179 | value = bind 'use_activity' 180 | }, 181 | viewFactory:combo_box { 182 | value = bind 'activity', 183 | items = activityList, 184 | auto_completion = true, 185 | enabled = bind 'use_activity', 186 | immediate = false, 187 | validate = function( view, value ) 188 | for _, v in pairs( activityList ) do 189 | if v == value then 190 | return true, value, "" 191 | end 192 | end 193 | return false, value, "Must be valid activity:\nStationary, Walking, Running, Biking, Eating, Automotive, Flying" 194 | end 195 | }, 196 | }, 197 | }, 198 | } 199 | end 200 | -------------------------------------------------------------------------------- /dayone.lrdevplugin/ExportTask.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2013, Philip Lundrigan 2 | -- All rights reserved. 3 | -- BSD License 4 | 5 | local random = math.random 6 | local LrPathUtils = import 'LrPathUtils' 7 | local LrFileUtils = import 'LrFileUtils' 8 | local LrDialogs = import 'LrDialogs' 9 | local LrDate = import 'LrDate' 10 | local LrStringUtils = import 'LrStringUtils' 11 | local LrXml = import 'LrXml' 12 | local uuid4= (loadfile(LrPathUtils.child(_PLUGIN.path, "uuid4.lua")))() 13 | 14 | local function uuid() 15 | local uuid = uuid4.getUUID() 16 | return string.gsub( uuid, '-', function (c) 17 | return '' 18 | end) 19 | end 20 | 21 | local function split( str, delimiter ) 22 | local result = { } 23 | local from = 1 24 | local delim_from, delim_to = string.find( str, delimiter, from ) 25 | while delim_from do 26 | table.insert( result, LrStringUtils.trimWhitespace( string.sub( str, from , delim_from-1 ) ) ) 27 | from = delim_to + 1 28 | delim_from, delim_to = string.find( str, delimiter, from ) 29 | end 30 | table.insert( result, LrStringUtils.trimWhitespace( string.sub( str, from ) ) ) 31 | return result 32 | end 33 | 34 | local function validJournalPath( path ) 35 | if not LrFileUtils.exists( path ) then 36 | return false, "Journal directory does not exist." 37 | elseif not LrFileUtils.exists( LrPathUtils.child(path, 'entries') ) then 38 | -- This directory should really be in here, if it is a valid journal 39 | -- Let's just error out and make the user pick a different directory 40 | return false, "\"entries\" directory does not exist." 41 | elseif not LrFileUtils.exists( LrPathUtils.child(path, 'photos') ) then 42 | -- When the user has not added a photo yet, the "photos" directory does not exist 43 | -- Let's just create it for them. 44 | LrFileUtils.createDirectory( LrPathUtils.child(path, 'photos') ) 45 | end 46 | 47 | return true, "" 48 | end 49 | 50 | local function getUniqueUUID( path ) 51 | local fileName = uuid() 52 | 53 | while LrFileUtils.exists( 54 | LrPathUtils.child( 55 | LrPathUtils.child( path, 'entries' ), fileName ) .. '.doentry' ) do 56 | fileName = uuid() 57 | end 58 | 59 | return fileName 60 | end 61 | 62 | local function getLocation( gps ) 63 | local LrHttp = import "LrHttp" 64 | 65 | local lat = gps.latitude 66 | local long = gps.longitude 67 | 68 | local url = "http://maps.googleapis.com/maps/api/geocode/xml?latlng=" .. lat .. "," .. long .. "&sensor=true" 69 | local xml = LrHttp.get( url ) 70 | 71 | root = LrXml.parseXml( xml ) 72 | status = root:childAtIndex( 1 ):text() 73 | 74 | local xsltString = [[ 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | , 85 | 86 | 87 | ]] 88 | 89 | local location = split( root:transform( xsltString ), ',' ) 90 | 91 | local results = {} 92 | results.placeName = location[1] 93 | results.locality = location[2] 94 | results.adminArea = location[4] 95 | results.country = location[5] 96 | results.latitude = lat 97 | results.longitude = long 98 | 99 | return results 100 | end 101 | 102 | local function formatTime( time ) 103 | tz, ds = LrDate.timeZone() 104 | if ds then 105 | time = time - tz - 3600 106 | else 107 | time = time - tz 108 | end 109 | 110 | local date = LrDate.timeToUserFormat( time, "%Y-%m-%d" ) 111 | local time = LrDate.timeToUserFormat( time, "%H:%M:%S" ) 112 | return date .. 'T' .. time .. 'Z' 113 | end 114 | 115 | local function generateEntry(date, starred, location, tags, uuid, activity) 116 | 117 | local entryString = [[ 118 | 119 | 120 | 121 | %s 122 | Creation Date 123 | %s 124 | Entry Text 125 | %s 126 | Starred 127 | <%s/>%s 128 | UUID 129 | %s 130 | 131 | 132 | ]] 133 | 134 | -- take care of activity if necessary 135 | local activityString = '' 136 | if activity ~= nil then 137 | activityString = [[ 138 | 139 | Activity 140 | %s]] 141 | 142 | activityString = string.format( activityString, activity) 143 | end 144 | 145 | -- take care of location if necessary 146 | local locationString = '' 147 | if location ~= nil then 148 | locationString = [[ 149 | 150 | Location 151 | 152 | Administrative Area 153 | %s 154 | Country 155 | %s 156 | Latitude 157 | %s 158 | Locality 159 | %s 160 | Longitude 161 | %s 162 | Place Name 163 | %s 164 | ]] 165 | 166 | locationString = string.format( locationString, 167 | location.adminArea, 168 | location.country, 169 | location.latitude, 170 | location.locality, 171 | location.longitude, 172 | location.placeName ) 173 | end 174 | 175 | -- take care of tags if necessary 176 | tag = '' 177 | if next(tags) ~= nil then 178 | tag = tag .. '\n\t' 179 | 180 | for key,value in pairs(tags) do 181 | tag = tag .. '\n\t\t' .. key .. '' 182 | end 183 | 184 | tag = tag .. '\n\t' 185 | else 186 | tag = '\n\t' 187 | end 188 | 189 | tagString = [[ 190 | 191 | Tags%s]] 192 | 193 | tagString = string.format( tagString, tag ) 194 | 195 | entryString = string.format( entryString, 196 | activityString, 197 | formatTime( date ), 198 | locationString, 199 | starred, 200 | tagString, 201 | uuid ) 202 | 203 | return entryString 204 | end 205 | 206 | local function createEntry( exportParams, photo, uuid ) 207 | local date = exportParams.use_time and 208 | photo:getRawMetadata("dateTimeOriginal") or 209 | LrDate.currentTime() 210 | 211 | -- get the correct path 212 | local entries = LrPathUtils.child( exportParams.path, 'entries' ) 213 | local path = LrPathUtils.child( LrPathUtils.standardizePath( entries ), uuid .. '.doentry' ) 214 | 215 | -- get the keywords 216 | local oldKeywords = exportParams.use_keywords and 217 | split( photo:getFormattedMetadata("keywordTags"), ',' ) or 218 | {} 219 | 220 | local newKeywords = exportParams.use_specific_tags and 221 | split( exportParams.tags, ',' ) or 222 | {} 223 | 224 | local activity = exportParams.use_activity and 225 | exportParams.activity or 226 | nil 227 | 228 | -- join two lists together 229 | local tags = {} 230 | for _, l in ipairs(oldKeywords) do 231 | if l ~= "" then 232 | tags[l] = true 233 | end 234 | end 235 | 236 | for _, l in ipairs(newKeywords) do 237 | if l ~= "" then 238 | tags[l] = true 239 | end 240 | end 241 | 242 | -- get location 243 | local location = nil 244 | if exportParams.use_location and photo:getRawMetadata("gps") then 245 | location = getLocation( photo:getRawMetadata("gps") ) 246 | end 247 | 248 | -- write entry 249 | local f = io.open( path, "w" ) 250 | f:write( generateEntry( date, exportParams.star, location, tags, uuid, activity )) 251 | f:close() 252 | 253 | end 254 | 255 | local function createPhoto( exportParams, photoPath, uuid ) 256 | local photos = LrPathUtils.child( exportParams.path, 'photos' ) 257 | LrFileUtils.copy( photoPath, LrPathUtils.child(LrPathUtils.standardizePath(photos), uuid .. '.jpg') ) 258 | end 259 | 260 | 261 | 262 | ExportTask = {} 263 | 264 | function ExportTask.processRenderedPhotos( functionContext, exportContext ) 265 | 266 | local exportSession = exportContext.exportSession 267 | local exportParams = exportContext.propertyTable 268 | 269 | local nPhotos = exportSession:countRenditions() 270 | local progressScope = exportContext:configureProgress { 271 | title = nPhotos > 1 and 272 | "Adding " .. nPhotos .. " photos to Day One" or 273 | "Adding one photo to Day One", 274 | } 275 | 276 | -- Check if selected journal location exists 277 | valid, errorMessage = validJournalPath( exportParams.path ) 278 | if not valid then 279 | LrDialogs.showError( "Something is wrong with the journal location \n(" .. exportParams.path .. ")\n you selected. " .. errorMessage) 280 | return 281 | end 282 | 283 | -- Iterate through photo renditions. 284 | for _, rendition in exportContext:renditions{ stopIfCanceled = true } do 285 | 286 | -- Wait for next photo to render. 287 | local success, pathOrMessage = rendition:waitForRender() 288 | 289 | if progressScope:isCanceled() then break end 290 | 291 | if success then 292 | local uuid = getUniqueUUID( exportParams.path ) 293 | 294 | createEntry( exportParams, rendition.photo, uuid ) 295 | createPhoto( exportParams, pathOrMessage, uuid ) 296 | 297 | -- clean up 298 | LrFileUtils.delete( pathOrMessage ) 299 | 300 | else 301 | LrDialogs.message( pathOrMessage ) 302 | end 303 | 304 | end 305 | end 306 | --------------------------------------------------------------------------------