├── .gitignore ├── date_converter.py ├── README.md └── notability_pdf_export.scpt /.gitignore: -------------------------------------------------------------------------------- 1 | # macOS 2 | .DS_Store 3 | 4 | # Don't include the generated pdfs 5 | out/* 6 | -------------------------------------------------------------------------------- /date_converter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | from datetime import datetime, timedelta 5 | 6 | def arguments(): 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument("datestring") 9 | return parser.parse_args() 10 | 11 | def create_expected_datestring(datestr): 12 | time_format = "%H:%M" 13 | date_format = "%d %b %Y" 14 | datetime_format = "{} {}".format(date_format, time_format) 15 | today = datetime.today() 16 | yesterday = today - timedelta(days=1) 17 | date_to_day_nr = lambda d: d.strftime(date_format) 18 | clean_datestr = datestr.replace( 19 | "at", "" 20 | ).replace( 21 | "Today", date_to_day_nr(today) 22 | ).replace( 23 | "Yesterday", date_to_day_nr(yesterday) 24 | ) 25 | 26 | correct_datetime = datetime.strptime(clean_datestr, datetime_format) 27 | return correct_datetime.strftime("%A, %d %B %Y at %H:%M:%S") 28 | 29 | if __name__ == "__main__": 30 | args = arguments() 31 | print(create_expected_datestring(args.datestring)) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 📌 Notability Notes Converter 📌 2 | 3 | This repository contains the necessary code to convert all of your notes to `.pdf` format. After running the script, all notes from the Notability app on your Mac will be exported as pdf to an `out` folder. 4 | 5 | ### ⚠️ Requirements ⚠️ 6 | 1. macOS 7 | 2. [Notability.app](https://www.gingerlabs.com) for macOS 8 | 9 | ## Running the script 10 | 11 | 1. Append a pin emoji (📌) to each note file that you wan't to have exported. 12 | 2. (Optional if editing notes from another Apple device) Wait for iCloud to sync. 13 | 3. Then run from terminal 14 | 15 | ``` 16 | $ osascript notability_pdf_export.scpt 17 | ``` 18 | 19 | Or just run the file directly via Script Editor. 20 | 21 | ### Example 22 | 23 | Structure of notes in Notability 24 | ``` 25 | subject_1/note1📌.note 26 | subject_1/note2📌.note 27 | subject_1/note3📌.note 28 | subject_1/note4.note 29 | subject_2/example_note📌.note 30 | subject_3/private_note.note 31 | ``` 32 | 33 | ``` 34 | $ osascript notability_pdf_export.scpt 35 | Success 36 | $ 37 | ``` 38 | 39 | After running the script a new directory containing all the required pdfs is created. 40 | 41 | ``` 42 | # Structure of out folder 43 | out/subject_1/note1.pdf 44 | out/subject_1/note2.pdf 45 | out/subject_1/note3.pdf 46 | out/subject_2/example_note.pdf 47 | ``` 48 | 49 | ## Features 50 | 51 | * Creates a `.pdf` file for each note that ends with the pin emoji (📌) 52 | * Each pdf file will be correctly categorized by a folder that represents the Note subject 53 | * The script doesn't export the same note twice. (i.e. the pdf files are overwritten only when the modification time of the Notability note is greater than the pdfs that were generated beforehand) 54 | ### Todo 55 | 56 | - [ ] Add flags for including the Paper 📜 / Page Margin 📄 in the pdf export settings. Currently each pdf is exported without the paper and page margins. 57 | - [ ] Add the option to flag (📌) an entire subject. 58 | - [ ] Add exclusion tag for notes that you don't want to be exported from tagged subjects. 59 | 60 | ## License 61 | 62 | MIT 63 | -------------------------------------------------------------------------------- /notability_pdf_export.scpt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/osascript 2 | 3 | property exportFileExtension : ".pdf" 4 | property noteExportFlag : "📌" 5 | property notabilityName : "Notability" 6 | 7 | # Create the out folder 8 | tell application "Finder" 9 | set outFolder to my FilepathToThisFolderAsString() & "out" 10 | end tell 11 | 12 | # Launch Notability 13 | tell application notabilityName 14 | quit 15 | delay 1 16 | launch 17 | delay 2 18 | activate 19 | end tell 20 | 21 | tell application "System Events" 22 | my WaitUntilExists(window 1 of application process notabilityName) 23 | # Save notability main window for later 24 | set notabilityWindow to window 1 of application process notabilityName 25 | tell process notabilityName 26 | # This selects the All Notes tab 27 | select (row 1 of outline 1 of scroll area 2 of window 1) 28 | 29 | # For each note in All notes 30 | repeat with aRow in (every row of table 1 of scroll area 3 of notabilityWindow) 31 | repeat 1 times 32 | # Variables 33 | set currentNoteCategory to name of static text 1 of UI element 1 of aRow 34 | set currentNoteName to name of static text 2 of UI element 1 of aRow 35 | set currentNoteModDate to name of static text 3 of UI element of aRow 36 | set currentNoteModDate to my CreateDateFromDatestring(currentNoteModDate as text) 37 | 38 | # if current row contains export flag 39 | if (currentNoteName contains noteExportFlag) then 40 | # create a new name for the note without the export flag 41 | set noteNameWithoutEmoji to text 1 thru -2 of currentNoteName 42 | # create the out filename for our note 43 | set outNoteFilename to noteNameWithoutEmoji & exportFileExtension 44 | set outFolder to outFolder & "/" & currentNoteCategory 45 | set outNoteFilepath to outFolder & "/" & outNoteFilename 46 | # create dir if needed 47 | do shell script "mkdir -p " & quoted form of outFolder 48 | 49 | 50 | # check if note already exists 51 | set noteFileExists to my FileExists(outNoteFilepath) 52 | if noteFileExists then 53 | # check if the current date is newer 54 | tell application "System Events" to set lastLocalModDate to modification date of file outNoteFilepath 55 | if lastLocalModDate ≥ currentNoteModDate then 56 | # continue the loop, we don't have to export this 57 | exit repeat -- continue 58 | end if 59 | end if 60 | 61 | # select it, so the export knows what to export 62 | select aRow 63 | 64 | # export 65 | click menu item "PDF..." of menu 1 of menu item "Export As" of menu 1 of menu bar item "File" of menu bar 1 66 | 67 | set exportDialogWindow to window "Export" 68 | my WaitUntilExists(exportDialogWindow) 69 | 70 | # handle Export dialog 71 | tell (exportDialogWindow) 72 | # set focus on the text field 73 | set focused of text field 1 to true 74 | 75 | # Change the output folder 76 | keystroke "g" using {command down, shift down} 77 | delay 0.1 78 | my WaitUntilExists(sheet 1) 79 | tell sheet 1 80 | set value of combo box 1 to outFolder 81 | click button "Go" 82 | end tell 83 | 84 | # set the new filename without emojis 85 | set value of text field 1 to outNoteFilename 86 | 87 | # we don't want page margins or the paper to be included in the final pdf (uncheck all checkboxes) 88 | repeat with aCheckbox in (every checkbox) 89 | tell aCheckbox to if value is 1 then click 90 | end repeat 91 | 92 | # finally, export 93 | click button "Export" 94 | 95 | # if the file exists 96 | if noteFileExists then 97 | # wait for the action sheet 98 | my WaitUntilExists(sheet 1) 99 | # click the button 100 | click button "Replace" of sheet 1 101 | end if 102 | end tell 103 | end if 104 | end repeat 105 | end repeat 106 | end tell 107 | end tell 108 | 109 | tell application notabilityName to quit 110 | 111 | return "Success" 112 | 113 | 114 | # Sub-routines 115 | on FileExists(theFile) -- (String) as Boolean 116 | tell application "System Events" 117 | return (exists file theFile) 118 | end tell 119 | end FileExists 120 | 121 | on CreateDateFromDatestring(theString) 122 | return date (do shell script "python3 " & my FilepathToThisFolderAsString() & "date_converter.py " & quoted form of theString) 123 | end CreateDateFromDatestring 124 | 125 | on FilepathToThisFolderAsString() 126 | tell application "Finder" 127 | return POSIX path of (parent of (path to me) as string) 128 | end tell 129 | end FilepathToThisFolderAsString 130 | 131 | on WaitUntilExists(theElement) 132 | tell current application 133 | repeat until (exists theElement) 134 | delay 1 135 | end repeat 136 | end tell 137 | end WaitUntilExists 138 | --------------------------------------------------------------------------------