├── images
├── image0.png
├── image1.png
├── image2.png
├── image3.png
├── image4.png
├── image5.png
├── image6.png
├── image7.png
├── image8.png
└── screenshots
│ ├── screen1.png
│ ├── screen2.png
│ ├── screen3.png
│ ├── screen4.png
│ └── screen5.png
├── Katip
├── Assets.xcassets
│ ├── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── katip_16x16.png
│ │ ├── katip_32x32.png
│ │ ├── katip_64x64.png
│ │ ├── katip_1024x1024.png
│ │ ├── katip_128x128.png
│ │ ├── katip_256x256.png
│ │ ├── katip_512x512.png
│ │ └── Contents.json
│ └── AccentColor.colorset
│ │ └── Contents.json
├── en.lproj
│ ├── InfoPlist.strings
│ └── Localization.strings
├── de.lproj
│ ├── InfoPlist.strings
│ ├── Localization.strings
│ └── MainMenu.strings
├── Katip.entitlements
├── ISOKatip+NSTableViewDataSource.swift
├── ISOKatip+NSTableViewDelegate.swift
├── AppDelegate.swift
├── Info.plist
├── ISOTranscriber.swift
├── ISOKatip.swift
└── Base.lproj
│ └── MainMenu.xib
├── Katip.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcuserdata
│ │ └── iso.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── xcuserdata
│ └── iso.xcuserdatad
│ │ ├── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── project.pbxproj
├── .editorconfig
├── .swiftformat
├── LICENSE.txt
├── HELP.md
├── .swiftlint.yml
└── README.md
/images/image0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/images/image0.png
--------------------------------------------------------------------------------
/images/image1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/images/image1.png
--------------------------------------------------------------------------------
/images/image2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/images/image2.png
--------------------------------------------------------------------------------
/images/image3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/images/image3.png
--------------------------------------------------------------------------------
/images/image4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/images/image4.png
--------------------------------------------------------------------------------
/images/image5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/images/image5.png
--------------------------------------------------------------------------------
/images/image6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/images/image6.png
--------------------------------------------------------------------------------
/images/image7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/images/image7.png
--------------------------------------------------------------------------------
/images/image8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/images/image8.png
--------------------------------------------------------------------------------
/images/screenshots/screen1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/images/screenshots/screen1.png
--------------------------------------------------------------------------------
/images/screenshots/screen2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/images/screenshots/screen2.png
--------------------------------------------------------------------------------
/images/screenshots/screen3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/images/screenshots/screen3.png
--------------------------------------------------------------------------------
/images/screenshots/screen4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/images/screenshots/screen4.png
--------------------------------------------------------------------------------
/images/screenshots/screen5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/images/screenshots/screen5.png
--------------------------------------------------------------------------------
/Katip/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Katip/Assets.xcassets/AppIcon.appiconset/katip_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/Katip/Assets.xcassets/AppIcon.appiconset/katip_16x16.png
--------------------------------------------------------------------------------
/Katip/Assets.xcassets/AppIcon.appiconset/katip_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/Katip/Assets.xcassets/AppIcon.appiconset/katip_32x32.png
--------------------------------------------------------------------------------
/Katip/Assets.xcassets/AppIcon.appiconset/katip_64x64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/Katip/Assets.xcassets/AppIcon.appiconset/katip_64x64.png
--------------------------------------------------------------------------------
/Katip/Assets.xcassets/AppIcon.appiconset/katip_1024x1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/Katip/Assets.xcassets/AppIcon.appiconset/katip_1024x1024.png
--------------------------------------------------------------------------------
/Katip/Assets.xcassets/AppIcon.appiconset/katip_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/Katip/Assets.xcassets/AppIcon.appiconset/katip_128x128.png
--------------------------------------------------------------------------------
/Katip/Assets.xcassets/AppIcon.appiconset/katip_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/Katip/Assets.xcassets/AppIcon.appiconset/katip_256x256.png
--------------------------------------------------------------------------------
/Katip/Assets.xcassets/AppIcon.appiconset/katip_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/Katip/Assets.xcassets/AppIcon.appiconset/katip_512x512.png
--------------------------------------------------------------------------------
/Katip.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Katip/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Katip.xcodeproj/xcuserdata/iso.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/Katip.xcodeproj/project.xcworkspace/xcuserdata/iso.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i-celeste-aurora/katip/HEAD/Katip.xcodeproj/project.xcworkspace/xcuserdata/iso.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/Katip/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /*
2 | InfoPlist.strings
3 | Katip
4 |
5 | Created by Imdat Solak on 27.02.21.
6 |
7 | */
8 | NSSpeechRecognitionUsageDescription = "We use speech recognition as main functionality here";
9 |
10 |
--------------------------------------------------------------------------------
/Katip/de.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /*
2 | InfoPlist.strings
3 | Katip
4 |
5 | Created by Imdat Solak on 27.02.21.
6 |
7 | */
8 | NSSpeechRecognitionUsageDescription = "Wir benötigen die Spracherkennung des macOS als Hauptfunktion in dieser Anwendung.";
9 |
10 |
--------------------------------------------------------------------------------
/Katip.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Katip/Katip.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-write
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = true
10 | indent_style = space
11 | indent_size = 2
12 | charset = utf-8
13 | max_line_length = 132
14 | trim_trailing_whitespace = true
15 |
16 |
--------------------------------------------------------------------------------
/Katip.xcodeproj/xcuserdata/iso.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Katip.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Katip/ISOKatip+NSTableViewDataSource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ISOKatip+NSTableViewDataSource.swift
3 | // Katip
4 | //
5 | // Created by Imdat Solak on 27.02.21.
6 | //
7 |
8 | import Foundation
9 | import AppKit
10 |
11 | extension ISOKatip: NSTableViewDataSource {
12 | func numberOfRows(in: NSTableView) -> Int {
13 | self.transcriber?.supportedLocales.count ?? 0
14 | }
15 |
16 | func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
17 | if let locale = self.transcriber?.supportedLocales[row] {
18 | return localizedNameForLocale(locale)
19 | } else {
20 | return "??"
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Katip/ISOKatip+NSTableViewDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ISOKatip+NSTableViewDelegate.swift
3 | // Katip
4 | //
5 | // Created by Imdat Solak on 27.02.21.
6 | //
7 |
8 | import Foundation
9 | import AppKit
10 |
11 | extension ISOKatip: NSTableViewDelegate {
12 | func tableView(_ tableView: NSTableView, shouldSelect tableColumn: NSTableColumn?) -> Bool {
13 | false
14 | }
15 |
16 | func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool {
17 | true
18 | }
19 |
20 | func tableView(_ tableView: NSTableView, shouldEdit tableColumn: NSTableColumn?, row: Int) -> Bool {
21 | false
22 | }
23 |
24 | func selectionShouldChange(in tableView: NSTableView) -> Bool {
25 | true
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.swiftformat:
--------------------------------------------------------------------------------
1 | --allman false
2 | --binarygrouping 4,8
3 | --closingparen balanced
4 | --commas always
5 | --conflictmarkers reject
6 | --decimalgrouping 3,3
7 | --elseposition same-line
8 | --empty void
9 | --exponentcase lowercase
10 | --exponentgrouping disabled
11 | --fractiongrouping disabled
12 | --fragment false
13 | --header ignore
14 | --hexgrouping 4,8
15 | --hexliteralcase uppercase
16 | --ifdef indent
17 | --importgrouping alphabetized
18 | --indent 4
19 | --indentcase false
20 | --linebreaks lf
21 | --octalgrouping 4,8
22 | --operatorfunc spaced
23 | --patternlet hoist
24 | --ranges spaced
25 | --self remove
26 | --selfrequired
27 | --semicolons inline
28 | --stripunusedargs always
29 | --trailingclosures
30 | --trimwhitespace always
31 | --wraparguments preserve
32 | --wrapcollections preserve
33 | --disable fileHeader,redundantInit,redundantSelf,trailingClosures,trailingCommas,specifiers
34 |
35 | --exclude Pods
36 |
--------------------------------------------------------------------------------
/Katip/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Katip
4 | //
5 | // Created by Imdat Solak on 24.01.21.
6 | //
7 |
8 | import Cocoa
9 | import UserNotifications
10 |
11 | @main
12 | class AppDelegate: NSObject, NSApplicationDelegate {
13 | @IBOutlet var window: NSWindow!
14 | var isActive: Bool = false
15 |
16 | func applicationDidFinishLaunching(_ aNotification: Notification) {
17 | UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
18 | if success {
19 | print("All set!")
20 | } else if let error = error {
21 | print(error.localizedDescription)
22 | }
23 | }
24 | }
25 |
26 | func applicationWillTerminate(_ aNotification: Notification) {
27 | // Insert code here to tear down your application
28 | }
29 |
30 | func applicationDidBecomeActive(_ notification: Notification) {
31 | isActive = true
32 | }
33 |
34 | func applicationDidResignActive(_ notification: Notification) {
35 | isActive = false
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Katip/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | Katip
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIconFile
12 |
13 | CFBundleIdentifier
14 | $(PRODUCT_BUNDLE_IDENTIFIER)
15 | CFBundleInfoDictionaryVersion
16 | 6.0
17 | CFBundleName
18 | $(PRODUCT_NAME)
19 | CFBundlePackageType
20 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
21 | CFBundleShortVersionString
22 | $(MARKETING_VERSION)
23 | CFBundleVersion
24 | 110
25 | LSApplicationCategoryType
26 | public.app-category.productivity
27 | LSMinimumSystemVersion
28 | $(MACOSX_DEPLOYMENT_TARGET)
29 | NSHumanReadableCopyright
30 | Copyright (2021) Imdat Solak. All Rights Reserved.
31 | NSMainNibFile
32 | MainMenu
33 | NSPrincipalClass
34 | NSApplication
35 | NSSpeechRecognitionUsageDescription
36 | We use speech recognition as main functionality here
37 |
38 |
39 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright 2021 Imdat Solak
2 |
3 | Redistribution and use in source and binary forms, with or without modification, are
4 | permitted provided that the following conditions are met:
5 |
6 | 1. Redistributions of source code must retain the above copyright notice, this list
7 | of conditions and the following disclaimer.
8 |
9 | 2. Redistributions in binary form must reproduce the above copyright notice, this
10 | list of conditions and the following disclaimer in the documentation and/or other
11 | materials provided with the distribution.
12 |
13 | 3. Neither the name of the copyright holder nor the names of its contributors may be
14 | used to endorse or promote products derived from this software without specific
15 | prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
18 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
20 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
22 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
23 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
25 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
26 | DAMAGE.
27 |
--------------------------------------------------------------------------------
/Katip/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "katip_16x16.png",
5 | "idiom" : "mac",
6 | "scale" : "1x",
7 | "size" : "16x16"
8 | },
9 | {
10 | "filename" : "katip_32x32.png",
11 | "idiom" : "mac",
12 | "scale" : "2x",
13 | "size" : "16x16"
14 | },
15 | {
16 | "filename" : "katip_32x32.png",
17 | "idiom" : "mac",
18 | "scale" : "1x",
19 | "size" : "32x32"
20 | },
21 | {
22 | "filename" : "katip_64x64.png",
23 | "idiom" : "mac",
24 | "scale" : "2x",
25 | "size" : "32x32"
26 | },
27 | {
28 | "filename" : "katip_128x128.png",
29 | "idiom" : "mac",
30 | "scale" : "1x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "filename" : "katip_256x256.png",
35 | "idiom" : "mac",
36 | "scale" : "2x",
37 | "size" : "128x128"
38 | },
39 | {
40 | "filename" : "katip_256x256.png",
41 | "idiom" : "mac",
42 | "scale" : "1x",
43 | "size" : "256x256"
44 | },
45 | {
46 | "filename" : "katip_512x512.png",
47 | "idiom" : "mac",
48 | "scale" : "2x",
49 | "size" : "256x256"
50 | },
51 | {
52 | "filename" : "katip_512x512.png",
53 | "idiom" : "mac",
54 | "scale" : "1x",
55 | "size" : "512x512"
56 | },
57 | {
58 | "filename" : "katip_1024x1024.png",
59 | "idiom" : "mac",
60 | "scale" : "2x",
61 | "size" : "512x512"
62 | }
63 | ],
64 | "info" : {
65 | "author" : "xcode",
66 | "version" : 1
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/HELP.md:
--------------------------------------------------------------------------------
1 | # Katip Usage
2 |
3 | After downloading and installing *Katip* by dragging it into your `Applications`-folder, just double-click to start it.
4 |
5 | There is only one window.
6 |
7 | ## Preferences
8 |
9 | In the preferences, you can choose your favorite languages that you want to be shown in the language popup in the main window.
10 |
11 | You can also reset the ETA assumptions as described in the [README file](README.md). See screenshot:
12 |
13 | 
14 |
15 | ## Main Window
16 |
17 | Please choose an audio file by clicking on the **Open**-button as shown in the below screenshot.
18 |
19 | 
20 |
21 | After choosing a file and clicking on **Transcribe**, you will see the following dialog:
22 |
23 | 
24 |
25 | Click **OK** and everything will be fine...
26 |
27 | 
28 |
29 | > WARNING: It will take some time for the first text to materialize in the window, please be patient!
30 |
31 | Once your transcription is finished, you can click on **Save Text...** to save the trascription of your
32 | dictation.
33 |
34 | 
35 |
36 | That's it, there is not much more. If you don't like the transcription, you can **Retry**. Sometimes this
37 | delivers different results, but it is very rarely.
38 |
39 | **NOTE**: You can, at any time, cancel the transcription. You can also, during the transcription, put *Katip* into
40 | background and continue your daily work. It will notify you with when it is ready (if you have enabled notifications).
41 |
42 | Have fun!
43 |
--------------------------------------------------------------------------------
/Katip/en.lproj/Localization.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localized.strings
3 | Katip
4 |
5 | Created by Imdat Solak on 24.01.21.
6 |
7 | */
8 |
9 | "message.transcribing" = "Transcribing « %@ »";
10 | "message.transcription.done" = "Transcription done...";
11 | "message.no_languages" = "No Languages";
12 | "message.waiting" = "Waiting...";
13 | "message.will.stop" = "Will Stop...";
14 | "window.title.stopping" = "Katip - Stopping...";
15 |
16 | "transcription.info.dialog.title" = "Transcription";
17 | "transcription.info.dialog.message" = "The transcription will be processed in realtime in the background, so you may continue with other work while I transcribe. I will notify you when it is done.";
18 | "transcription.done.notification.title" = "Transcription done";
19 | "transcription.done.notification.message" = "Your transcription of « %@ » is done.";
20 | "openpanel.title" = "Select Recording";
21 | "openpanel.prompt" = "Transcribe";
22 | "openpanel.message" = "Select a Recording to Transcribe";
23 | "errormessage.speech.denied" = "Speech Recognition Denied";
24 | "errormessage.speech.restricted" = "Speech Recognition Restricted";
25 | "errormessage.speech.not_determined" = "Speech Recognition Not Determined";
26 | "errormessage.speech.unknown" = "Speech Recognition Status Unknown";
27 | "savepanel.title" = "Save Transcription";
28 | "savepanel.prompt" = "Save";
29 | "savepanel.message" = "Select a filename to save to";
30 | "savepanel.untitle" = "Untitled";
31 |
32 | "saveerror.dialog.title" = "File Save Error";
33 | "saveerror.dialog.message" = "Could not save transcribed text to the specified file. Please check permission and try again.";
34 |
35 | "message.eta" = "ETA: %02d:%02d:%02d";
36 |
37 | "reset.eta.estimates.alert.title" = "Reset ETA Estimates";
38 | "reset.eta.estimates.message" = "Are you sure you want to reset ETA estimate data?";
39 | "reset.eta.estimates.reset.button" = "Reset";
40 | "reset.eta.estimates.cancel.button" = "Cancel";
41 |
--------------------------------------------------------------------------------
/Katip/de.lproj/Localization.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localized.strings
3 | Katip
4 |
5 | Created by Imdat Solak on 24.01.21.
6 |
7 | */
8 |
9 | "message.transcribing" = "Transkribiere « %@ »";
10 | "message.transcription.done" = "Transkription fertig...";
11 | "message.no_languages" = "Keine Transkriptionssprachen verfügbar";
12 | "message.waiting" = "Warten...";
13 | "message.will.stop" = "Wird angehalten...";
14 | "window.title.stopping" = "Katip - Hält an...";
15 |
16 | "transcription.info.dialog.title" = "Transkription";
17 | "transcription.info.dialog.message" = "Die Transkription wird im Hintergrund durchgeführt. Sie können währenddessen sich mit anderen Themen beschäftigen. Ich werede Sie informieren, wenn die Transkription fertig ist.";
18 | "transcription.done.notification.title" = "Transkription fertig";
19 | "transcription.done.notification.message" = "Ihre Transkription von « %@ » ist fertig.";
20 | "openpanel.title" = "Sprachaufzeichnung auswählen";
21 | "openpanel.prompt" = "Transkribieren";
22 | "openpanel.message" = "Wählen Sie eine Sprachaufzeichnung aus";
23 | "errormessage.speech.denied" = "Spracherkennung abgelehnt";
24 | "errormessage.speech.restricted" = "Spracherkennung eingeschränkt";
25 | "errormessage.speech.not_determined" = "Spracherkennungsmöglichkeit nicht bekannt";
26 | "errormessage.speech.unknown" = "Spracherkennungsmöglichkeit nicht bekannt";
27 | "savepanel.title" = "Transkription speichern";
28 | "savepanel.prompt" = "Speichern";
29 | "savepanel.message" = "Bitte wählen Sie einen Dateinamen aus";
30 | "savepanel.untitle" = "Untitled";
31 |
32 | "saveerror.dialog.title" = "Fehler beim Speichern";
33 | "saveerror.dialog.message" = "Die Transkription konnte nicht gespeichert werden. Bitte überprüfen Sie die Zugriffsrechte und versuchen Sie es nochmals.";
34 |
35 | "message.eta" = "ETA: %02d:%02d:%02d";
36 |
37 | "reset.eta.estimates.alert.title" = "ETA Zurücksetzen";
38 | "reset.eta.estimates.message" = "Sind Sie sicher, dass sie die ETA-Annahmen zurücksetzen möchten?";
39 | "reset.eta.estimates.reset.button" = "Zurück setzen";
40 | "reset.eta.estimates.cancel.button" = "Abbrechen";
41 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules: # rule identifiers to exclude from running
2 | - type_name
3 | - identifier_name
4 | - opening_brace
5 | - line_length
6 | - type_body_length
7 | - function_body_length
8 | - file_length
9 | - cyclomatic_complexity
10 | - weak_delegate
11 | - nesting
12 | #- discarded_notification_center_observer
13 | - statement_position
14 | - trailing_whitespace
15 | - function_parameter_count
16 | - todo
17 | - switch_case_alignment
18 | opt_in_rules:
19 | - array_init
20 | - attributes
21 | - closure_spacing
22 | ## - conditional_returns_on_newline
23 | - contains_over_first_not_nil
24 | - convenience_type
25 | - discouraged_object_literal
26 | # - empty_count
27 | ## - explicit_acl
28 | ## - explicit_enum_raw_value
29 | - explicit_init
30 | ## - explicit_top_level_acl
31 | ## - explicit_type_interface
32 | ## - extension_access_modifier
33 | ## - file_header
34 | - first_where
35 | - force_unwrapping
36 | - function_default_parameter_at_end
37 | - implicit_return
38 | # - implicitly_unwrapped_optional
39 | - joined_default_parameter
40 | ## - let_var_whitespace
41 | ## - literal_expression_end_indentation
42 | ## - modifier_order
43 | ## - multiline_arguments
44 | - multiline_function_chains
45 | - multiline_parameters
46 | - nimble_operator
47 | ## - no_extension_access_modifier
48 | ## - no_grouping_extension
49 | - number_separator
50 | - object_literal
51 | - operator_usage_whitespace
52 | - overridden_super_call
53 | - override_in_extension
54 | - pattern_matching_keywords
55 | # - prefixed_toplevel_constant
56 | - private_action
57 | # - private_outlet
58 | - prohibited_super_call
59 | - quick_discouraged_call
60 | - quick_discouraged_focused_test
61 | - quick_discouraged_pending_test
62 | - redundant_nil_coalescing
63 | - required_enum_case
64 | - single_test_class
65 | - sorted_first_last
66 | # - sorted_imports
67 | ## - strict_fileprivate
68 | - switch_case_on_newline
69 | #- trailing_closure
70 | - unavailable_function
71 | # - unneeded_parentheses_in_closure_argument
72 | - vertical_parameter_alignment_on_call
73 | - yoda_condition
74 | force_cast: warning # implicitly
75 |
76 | excluded: # paths to ignore during linting. Takes precedence over `included`.
77 | - Pods
78 |
--------------------------------------------------------------------------------
/Katip/ISOTranscriber.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ISOTranscriber.swift
3 | // Katip
4 | //
5 | // Created by Imdat Solak on 24.01.21.
6 | //
7 |
8 | import Foundation
9 | import AVFoundation
10 | import Accelerate
11 | import Speech
12 |
13 | protocol ISOTranscriberDelegate: AnyObject {
14 | func setTranscribedText(_ text: String, withSentenceNo sentenceNo: Int, fromTranscription: SFTranscription)
15 | func shouldStopTranscription() -> Bool
16 | func transcriptionDone()
17 | func transcriptionCanceled()
18 | func speechRecognitionAuthorized()
19 | func speechRecognitionAuthorizationFailed(_ error: String)
20 | }
21 |
22 | class ISOTranscriber: NSObject {
23 | var delegate: ISOTranscriberDelegate?
24 | private var recognizer: SFSpeechRecognizer!
25 | private var recognitionTask: SFSpeechRecognitionTask!
26 | private var currentSentenceNo: Int = 0
27 | private var previousResult: SFSpeechRecognitionResult?
28 |
29 | var supportedLocales: [Locale] = []
30 | var recognitionEnabled: Bool = false
31 |
32 | override
33 | init() {
34 | super.init()
35 | var availableLocales = Array(SFSpeechRecognizer.supportedLocales())
36 | availableLocales.sort {
37 | $0.identifier < $1.identifier
38 | }
39 | for locale in availableLocales {
40 | self.supportedLocales.append(locale)
41 | if let recognizer = SFSpeechRecognizer(locale: locale), recognizer.supportsOnDeviceRecognition {
42 | NSLog("\(locale) supported for onDeviceRecognition")
43 | } else {
44 | NSLog("Locale not supported \(locale)")
45 | }
46 | }
47 | }
48 |
49 | func setup() {
50 | SFSpeechRecognizer.requestAuthorization { authStatus in
51 | OperationQueue.main.addOperation {
52 | switch authStatus {
53 | case .authorized:
54 | self.recognitionEnabled = true
55 | self.delegate?.speechRecognitionAuthorized()
56 | case .denied:
57 | self.recognitionEnabled = false
58 | self.delegate?.speechRecognitionAuthorizationFailed("errormessage.speech.denied")
59 | case .restricted:
60 | self.recognitionEnabled = false
61 | self.delegate?.speechRecognitionAuthorizationFailed("errormessage.speech.restricted")
62 | case .notDetermined:
63 | self.recognitionEnabled = false
64 | self.delegate?.speechRecognitionAuthorizationFailed("errormessage.speech.not_determined")
65 | @unknown default:
66 | self.delegate?.speechRecognitionAuthorizationFailed("errormessage.speech.unknown")
67 | }
68 | }
69 | }
70 | }
71 |
72 | func startTranscription(_ recordingFileUrl: URL, usingLanguage language: String) {
73 | guard self.recognitionEnabled == true else { return }
74 | guard self.supportedLocales.count > 0 else { return }
75 | self.recognizer = SFSpeechRecognizer(locale: Locale(identifier: language))
76 | self.previousResult = nil
77 | let recognitionRequest = SFSpeechURLRecognitionRequest(url: recordingFileUrl)
78 | recognitionRequest.requiresOnDeviceRecognition = true
79 | recognitionRequest.shouldReportPartialResults = true
80 | recognitionRequest.taskHint = SFSpeechRecognitionTaskHint.dictation
81 | self.currentSentenceNo = 0
82 | self.recognitionTask = self.recognizer.recognitionTask(with: recognitionRequest, delegate: self)
83 | }
84 | }
85 |
86 | extension ISOTranscriber: SFSpeechRecognitionTaskDelegate {
87 | func speechRecognitionDidDetectSpeech(_ task: SFSpeechRecognitionTask) {
88 | NSLog("Detected Speech")
89 | }
90 |
91 | func speechRecognitionTaskFinishedReadingAudio(_ task: SFSpeechRecognitionTask) {
92 | NSLog("speechRecognitionTaskFinishedReadingAudio")
93 | }
94 |
95 | func speechRecognitionTask(_ task: SFSpeechRecognitionTask, didHypothesizeTranscription transcription: SFTranscription) {
96 | self.delegate?.setTranscribedText(transcription.formattedString, withSentenceNo: self.currentSentenceNo, fromTranscription: transcription)
97 | if self.delegate?.shouldStopTranscription() ?? false {
98 | self.recognitionTask.cancel()
99 | self.recognitionTask = nil
100 | }
101 | }
102 |
103 | func speechRecognitionTask(_ task: SFSpeechRecognitionTask, didFinishRecognition recognitionResult: SFSpeechRecognitionResult) {
104 | self.delegate?.setTranscribedText(recognitionResult.bestTranscription.formattedString, withSentenceNo: self.currentSentenceNo, fromTranscription: recognitionResult.bestTranscription)
105 | self.currentSentenceNo += 1
106 | if self.delegate?.shouldStopTranscription() ?? false {
107 | self.recognitionTask.cancel()
108 | self.recognitionTask = nil
109 | }
110 | }
111 |
112 | func speechRecognitionTask(_ task: SFSpeechRecognitionTask, didFinishSuccessfully successfully: Bool) {
113 | NSLog("DidFinishSuccessfully")
114 | self.recognitionTask = nil
115 | self.delegate?.transcriptionDone()
116 | }
117 |
118 | func speechRecognitionTaskWasCancelled(_ task: SFSpeechRecognitionTask) {
119 | NSLog("Cancelled")
120 | self.recognitionTask = nil
121 | self.delegate?.transcriptionCanceled()
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Katip
2 |
3 | A simple `SFSpeechRecognizer`-based Voice Recording transcriber for macOS.
4 |
5 | If you have voice recordings (dictations) that you would like to transcribe using
6 | your macOS computer, you can use this application to do so.
7 |
8 | The voice-recordings must be in `mp3`, `m4a`, `wav`, or `mpa`-format (mor formats
9 | may be possible, haven't tested yet).
10 |
11 | **Katip** will require local transcription capabilities; it does not use Apple's
12 | servers, otherwise the transcription would be limited to a few seconds.
13 |
14 | Depending on the performance of your local macOS computer, the transcription may
15 | happen in somewhere between realtime and 3-4x real-time speed. Please be patient.
16 |
17 | # The name
18 |
19 | You may ask what **Katip** means and why I called it that way and why there is a
20 | fez as application icon.
21 |
22 | The [wise Wikipedia has the answer](https://en.wikipedia.org/wiki/Uska_Dara):
23 |
24 | > Uska Dara (A Turkish Tale) is a 1953 song made famous by Eartha Kitt,
25 |
26 | ... which is, actually wrong. The original title is *"Üsküdar'a giderken"*, meaning
27 | *while on my way to Üsküdar*, where **Üsküdar** is district of Istanbul and
28 | in those days it was probably a suburb of Istanbul...
29 |
30 | > also recorded by Eydie Gormé. It is based on the Turkish folk song
31 | > Kâtibim about a woman and her secretary traveling to Üsküdar. On
32 | > early American recordings, this adaptation is credited to Stella Lee.
33 |
34 | Basically, *Kâtibim* means "my Katip" and "Katip" is a male secretary.
35 |
36 | In olden days in Turkey, many people could not or would not want to write themselves
37 | and thus there were *Katip* who would write for other people. Sometimes you had your
38 | own *Katip* (provided you were rich enough to pay for a full-time *Katip*).
39 |
40 | That's the background of the title: **Katip = your (male) secretary**
41 |
42 | I recommend to listen to the [Eartha Kitt version of the song on YouTube](https://www.youtube.com/watch?v=sVXFDtC_gqk).
43 |
44 | # Setup
45 |
46 | You need to tell macOS which languages you want to have offline transcription support. In this case,
47 | macOS will download a speech-data for your language and then you can perform your transcript.
48 | You have to do this for every language you want to use.
49 |
50 | To do so, follow the guidelines:
51 |
52 | ## macOS <= 10.15 (Catalina)
53 | ### 1. Select "System Preferences" from the Apple-Menu
54 | 
55 |
56 | ### 2. Select "Keyboard"
57 |
58 | 
59 |
60 | ### 3. Select "Dictation"
61 |
62 | 
63 |
64 | ### 4. Select "Customize..." from "Language"-Menu
65 |
66 | 
67 |
68 | ### 5. Choose your languages
69 |
70 | 
71 |
72 | ## macOS >= 11 (Big Sur)
73 | ### 1. Select "System Preferences" from the Apple-Menu
74 | 
75 |
76 | ### 2. Select "Accessibility"
77 | 
78 |
79 | ### 3. Select "Voice Control"
80 | 
81 |
82 | IMPORTANT: PLEASE SWITCH ON "Enable Voice Control", otherwise the transcription will not work.
83 |
84 | ### 4. Select "Customize..."
85 | 
86 |
87 | ### 5. Select the languages that you want to use
88 | 
89 |
90 | macOS will then download the language packs required. Please make sure those language packs are downloaded.
91 |
92 | Note: English (US) currently has some issues because Apple allows that one to only work on their servers.
93 |
94 | If you have any requests, please do not hesitate to open an issue.
95 |
96 | ## ETA Assumptions
97 | `SFSpeechRecognizer` can't estimate how long the transcription will take. The user,
98 | though, would like to know how long it *may* take. The solution I have come up is
99 | actually easy:
100 |
101 | > It seems, the time it takes is dependent on machine and load on the machine.
102 | > The load itself doesn't some to influence too much. So, the first time you
103 | > run the software on a machine in your account, it will show an **indeterminate**
104 | > progress bar. It will tell you that it might take as much as the duration of
105 | > your audio recording.
106 | >
107 | > At the end of each recording, it will check how long it actually took. And starting
108 | > with the second transcription, it will have better and better estimates and will
109 | > display a **determinate** progress bar.
110 | >
111 | > If you think the timing sucks, you can reset all assumptions in the preferences.
112 |
113 | ## Notes
114 |
115 | There might be more documentation on [Blog Post](https://www.solak.de/katip-an-sfspeechrecognizer-based-voice-recordings-transcriber-for-macos/).
116 |
117 | ## License
118 |
119 | Copyright 2021 Imdat Solak
120 |
121 | Redistribution and use in source and binary forms, with or without modification, are
122 | permitted provided that the following conditions are met:
123 |
124 | 1. Redistributions of source code must retain the above copyright notice, this list
125 | of conditions and the following disclaimer.
126 |
127 | 2. Redistributions in binary form must reproduce the above copyright notice, this
128 | list of conditions and the following disclaimer in the documentation and/or other
129 | materials provided with the distribution.
130 |
131 | 3. Neither the name of the copyright holder nor the names of its contributors may be
132 | used to endorse or promote products derived from this software without specific
133 | prior written permission.
134 |
135 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
136 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
137 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
138 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
139 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
140 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
141 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
142 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
143 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
144 | DAMAGE.
145 |
--------------------------------------------------------------------------------
/Katip/de.lproj/MainMenu.strings:
--------------------------------------------------------------------------------
1 |
2 | /* Class = "NSMenuItem"; title = "Find"; ObjectID = "0AN-w9-wvJ"; */
3 | "0AN-w9-wvJ.title" = "Find";
4 |
5 | /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "0Wb-QZ-ns5"; */
6 | "0Wb-QZ-ns5.title" = "Copy";
7 |
8 | /* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "1US-do-bxq"; */
9 | "1US-do-bxq.title" = "Make Lower Case";
10 |
11 | /* Class = "NSMenuItem"; title = "Katip"; ObjectID = "1Xt-HY-uBw"; */
12 | "1Xt-HY-uBw.title" = "Katip";
13 |
14 | /* Class = "NSMenuItem"; title = "Quit Katip"; ObjectID = "4sb-4s-VLi"; */
15 | "4sb-4s-VLi.title" = "Quit Katip";
16 |
17 | /* Class = "NSMenuItem"; title = "About Katip"; ObjectID = "5kV-Vb-QxS"; */
18 | "5kV-Vb-QxS.title" = "About Katip";
19 |
20 | /* Class = "NSMenuItem"; title = "Check Grammar With Spelling"; ObjectID = "7jn-q8-3py"; */
21 | "7jn-q8-3py.title" = "Check Grammar With Spelling";
22 |
23 | /* Class = "NSMenuItem"; title = "Data Detectors"; ObjectID = "7oJ-7m-10s"; */
24 | "7oJ-7m-10s.title" = "Data Detectors";
25 |
26 | /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "7xg-OE-VgE"; */
27 | "7xg-OE-VgE.title" = "Select All";
28 |
29 | /* Class = "NSMenuItem"; title = "Undo"; ObjectID = "8eS-BF-UBQ"; */
30 | "8eS-BF-UBQ.title" = "Undo";
31 |
32 | /* Class = "NSMenuItem"; title = "Item 2"; ObjectID = "9WM-fh-zUj"; */
33 | "9WM-fh-zUj.title" = "Item 2";
34 |
35 | /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */
36 | "AYu-sK-qS6.title" = "Main Menu";
37 |
38 | /* Class = "NSTextFieldCell"; title = "Language"; ObjectID = "Aap-W8-aEe"; */
39 | "Aap-W8-aEe.title" = "Language";
40 |
41 | /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */
42 | "BOF-NM-1cW.title" = "Preferences…";
43 |
44 | /* Class = "NSMenuItem"; title = "Show Spelling and Grammar"; ObjectID = "BVq-7D-7HU"; */
45 | "BVq-7D-7HU.title" = "Show Spelling and Grammar";
46 |
47 | /* Class = "NSMenuItem"; title = "Substitutions"; ObjectID = "Cds-zt-IgS"; */
48 | "Cds-zt-IgS.title" = "Substitutions";
49 |
50 | /* Class = "NSMenuItem"; title = "Find and Replace…"; ObjectID = "CgI-iS-IjE"; */
51 | "CgI-iS-IjE.title" = "Find and Replace…";
52 |
53 | /* Class = "NSMenuItem"; title = "Find…"; ObjectID = "EBI-kO-NG3"; */
54 | "EBI-kO-NG3.title" = "Find…";
55 |
56 | /* Class = "NSMenu"; title = "Spelling"; ObjectID = "EtP-i0-N7k"; */
57 | "EtP-i0-N7k.title" = "Spelling";
58 |
59 | /* Class = "NSMenuItem"; title = "Spelling and Grammar"; ObjectID = "Fkd-Jd-TdJ"; */
60 | "Fkd-Jd-TdJ.title" = "Spelling and Grammar";
61 |
62 | /* Class = "NSMenu"; title = "Edit"; ObjectID = "Hio-Uc-aaS"; */
63 | "Hio-Uc-aaS.title" = "Edit";
64 |
65 | /* Class = "NSMenu"; title = "Find"; ObjectID = "Iul-zV-oIa"; */
66 | "Iul-zV-oIa.title" = "Find";
67 |
68 | /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "JUD-9f-3BO"; */
69 | "JUD-9f-3BO.title" = "Paste and Match Style";
70 |
71 | /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */
72 | "Kd2-mp-pUS.title" = "Show All";
73 |
74 | /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "Kim-Vi-m24"; */
75 | "Kim-Vi-m24.title" = "Paste";
76 |
77 | /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */
78 | "LE2-aR-0XJ.title" = "Bring All to Front";
79 |
80 | /* Class = "NSTextFieldCell"; title = "ETA"; ObjectID = "M2G-jo-NOQ"; */
81 | "M2G-jo-NOQ.title" = "ETA";
82 |
83 | /* Class = "NSMenuItem"; title = "Check Document Now"; ObjectID = "MtV-KR-GmE"; */
84 | "MtV-KR-GmE.title" = "Check Document Now";
85 |
86 | /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */
87 | "NMo-om-nkz.title" = "Services";
88 |
89 | /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */
90 | "OY7-WF-poV.title" = "Minimize";
91 |
92 | /* Class = "NSMenuItem"; title = "Hide Katip"; ObjectID = "Olw-nP-bQN"; */
93 | "Olw-nP-bQN.title" = "Hide Katip";
94 |
95 | /* Class = "NSButtonCell"; title = "Retry"; ObjectID = "Pvn-lu-DWa"; */
96 | "Pvn-lu-DWa.title" = "Retry";
97 |
98 | /* Class = "NSMenuItem"; title = "Jump to Selection"; ObjectID = "Qu9-pJ-eIP"; */
99 | "Qu9-pJ-eIP.title" = "Jump to Selection";
100 |
101 | /* Class = "NSWindow"; title = "Katip"; ObjectID = "QvC-M9-y7g"; */
102 | "QvC-M9-y7g.title" = "Katip";
103 |
104 | /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */
105 | "R4o-n2-Eq4.title" = "Zoom";
106 |
107 | /* Class = "NSMenuItem"; title = "Correct Spelling Automatically"; ObjectID = "RGD-vR-3Ve"; */
108 | "RGD-vR-3Ve.title" = "Correct Spelling Automatically";
109 |
110 | /* Class = "NSMenuItem"; title = "Smart Quotes"; ObjectID = "SUN-zz-beK"; */
111 | "SUN-zz-beK.title" = "Smart Quotes";
112 |
113 | /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */
114 | "Td7-aD-5lo.title" = "Window";
115 |
116 | /* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "Trc-oQ-tRq"; */
117 | "Trc-oQ-tRq.title" = "Transformations";
118 |
119 | /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */
120 | "Vdr-fp-XzO.title" = "Hide Others";
121 |
122 | /* Class = "NSButtonCell"; title = "Save Text..."; ObjectID = "Vec-cX-mNt"; */
123 | "Vec-cX-mNt.title" = "Save Text...";
124 |
125 | /* Class = "NSMenu"; title = "Transformations"; ObjectID = "Vgq-tL-ccc"; */
126 | "Vgq-tL-ccc.title" = "Transformations";
127 |
128 | /* Class = "NSTextFieldCell"; placeholderString = ""; ObjectID = "XIo-4L-ZgG"; */
129 | "XIo-4L-ZgG.placeholderString" = "";
130 |
131 | /* Class = "NSMenuItem"; title = "Text Replacement"; ObjectID = "YTx-yG-okA"; */
132 | "YTx-yG-okA.title" = "Text Replacement";
133 |
134 | /* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "Yuh-RD-Qxa"; */
135 | "Yuh-RD-Qxa.title" = "Make Upper Case";
136 |
137 | /* Class = "NSMenuItem"; title = "Smart Links"; ObjectID = "ZDr-GB-SIR"; */
138 | "ZDr-GB-SIR.title" = "Smart Links";
139 |
140 | /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "ZFd-f0-7GL"; */
141 | "ZFd-f0-7GL.title" = "Cut";
142 |
143 | /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */
144 | "aUF-d1-5bR.title" = "Window";
145 |
146 | /* Class = "NSButtonCell"; title = "Stop"; ObjectID = "aWm-BA-lXi"; */
147 | "aWm-BA-lXi.title" = "Stop";
148 |
149 | /* Class = "NSMenuItem"; title = "Find Previous"; ObjectID = "bPl-hX-ouh"; */
150 | "bPl-hX-ouh.title" = "Find Previous";
151 |
152 | /* Class = "NSTextFieldCell"; title = "Voice Recording File"; ObjectID = "cS1-VU-S8X"; */
153 | "cS1-VU-S8X.title" = "Voice Recording File";
154 |
155 | /* Class = "NSMenuItem"; title = "File"; ObjectID = "dMs-cI-mzQ"; */
156 | "dMs-cI-mzQ.title" = "File";
157 |
158 | /* Class = "NSMenuItem"; title = "Check Spelling While Typing"; ObjectID = "h9T-g5-sf7"; */
159 | "h9T-g5-sf7.title" = "Check Spelling While Typing";
160 |
161 | /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */
162 | "hz9-B4-Xy5.title" = "Services";
163 |
164 | /* Class = "NSMenuItem"; title = "Item 1"; ObjectID = "i78-zU-eLa"; */
165 | "i78-zU-eLa.title" = "Item 1";
166 |
167 | /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "jMu-4B-lv7"; */
168 | "jMu-4B-lv7.title" = "Edit";
169 |
170 | /* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "knz-RA-2uw"; */
171 | "knz-RA-2uw.title" = "Capitalize";
172 |
173 | /* Class = "NSMenuItem"; title = "Smart Dashes"; ObjectID = "loY-Xq-Vbn"; */
174 | "loY-Xq-Vbn.title" = "Smart Dashes";
175 |
176 | /* Class = "NSMenuItem"; title = "Redo"; ObjectID = "n3p-A8-MPl"; */
177 | "n3p-A8-MPl.title" = "Redo";
178 |
179 | /* Class = "NSButtonCell"; title = "Open...."; ObjectID = "nhR-9F-D11"; */
180 | "nhR-9F-D11.title" = "Open....";
181 |
182 | /* Class = "NSTextFieldCell"; title = "Transcribing..."; ObjectID = "oeM-6D-vZg"; */
183 | "oeM-6D-vZg.title" = "Transcribing...";
184 |
185 | /* Class = "NSMenuItem"; title = "Show Substitutions"; ObjectID = "rHR-5m-WHK"; */
186 | "rHR-5m-WHK.title" = "Show Substitutions";
187 |
188 | /* Class = "NSMenuItem"; title = "Smart Copy/Paste"; ObjectID = "tD8-01-6rw"; */
189 | "tD8-01-6rw.title" = "Smart Copy/Paste";
190 |
191 | /* Class = "NSMenu"; title = "Katip"; ObjectID = "uQy-DD-JDr"; */
192 | "uQy-DD-JDr.title" = "Katip";
193 |
194 | /* Class = "NSMenuItem"; title = "Use Selection for Find"; ObjectID = "unz-Zx-N0t"; */
195 | "unz-Zx-N0t.title" = "Use Selection for Find";
196 |
197 | /* Class = "NSMenuItem"; title = "Find Next"; ObjectID = "ush-Oz-1hE"; */
198 | "ush-Oz-1hE.title" = "Find Next";
199 |
200 | /* Class = "NSMenuItem"; title = "Item 3"; ObjectID = "vaQ-OG-pFi"; */
201 | "vaQ-OG-pFi.title" = "Item 3";
202 |
203 | /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */
204 | "wpr-3q-Mcd.title" = "Help";
205 |
206 | /* Class = "NSMenu"; title = "Substitutions"; ObjectID = "xf3-7k-ROX"; */
207 | "xf3-7k-ROX.title" = "Substitutions";
208 |
209 | /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "yUw-eh-agP"; */
210 | "yUw-eh-agP.title" = "Delete";
211 |
--------------------------------------------------------------------------------
/Katip.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 726FB40825BD612F007BEAE9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 726FB40725BD612F007BEAE9 /* AppDelegate.swift */; };
11 | 726FB40A25BD6130007BEAE9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 726FB40925BD6130007BEAE9 /* Assets.xcassets */; };
12 | 726FB40D25BD6130007BEAE9 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 726FB40B25BD6130007BEAE9 /* MainMenu.xib */; };
13 | 726FB41C25BD640E007BEAE9 /* ISOKatip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 726FB41B25BD640E007BEAE9 /* ISOKatip.swift */; };
14 | 726FB41F25BD6975007BEAE9 /* ISOTranscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 726FB41E25BD6975007BEAE9 /* ISOTranscriber.swift */; };
15 | 726FB42625BD7FC0007BEAE9 /* Localization.strings in Resources */ = {isa = PBXBuildFile; fileRef = 726FB42825BD7FC0007BEAE9 /* Localization.strings */; };
16 | 7270219A25EA9BD70008200D /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7270219C25EA9BD70008200D /* InfoPlist.strings */; };
17 | 727021A125EAB3AE0008200D /* ISOKatip+NSTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 727021A025EAB3AE0008200D /* ISOKatip+NSTableViewDataSource.swift */; };
18 | 727021A425EAB4F10008200D /* ISOKatip+NSTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 727021A325EAB4F10008200D /* ISOKatip+NSTableViewDelegate.swift */; };
19 | /* End PBXBuildFile section */
20 |
21 | /* Begin PBXFileReference section */
22 | 726FB40425BD612F007BEAE9 /* Katip.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Katip.app; sourceTree = BUILT_PRODUCTS_DIR; };
23 | 726FB40725BD612F007BEAE9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
24 | 726FB40925BD6130007BEAE9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
25 | 726FB40C25BD6130007BEAE9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; };
26 | 726FB40E25BD6130007BEAE9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
27 | 726FB40F25BD6130007BEAE9 /* Katip.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Katip.entitlements; sourceTree = ""; };
28 | 726FB41B25BD640E007BEAE9 /* ISOKatip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ISOKatip.swift; sourceTree = ""; };
29 | 726FB41E25BD6975007BEAE9 /* ISOTranscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ISOTranscriber.swift; sourceTree = ""; };
30 | 726FB42725BD7FC0007BEAE9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localization.strings; sourceTree = ""; };
31 | 7270219425EA9A7A0008200D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/MainMenu.strings; sourceTree = ""; };
32 | 7270219525EA9A7A0008200D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localization.strings; sourceTree = ""; };
33 | 7270219B25EA9BD70008200D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; };
34 | 7270219E25EA9BE00008200D /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; };
35 | 727021A025EAB3AE0008200D /* ISOKatip+NSTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ISOKatip+NSTableViewDataSource.swift"; sourceTree = ""; };
36 | 727021A325EAB4F10008200D /* ISOKatip+NSTableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ISOKatip+NSTableViewDelegate.swift"; sourceTree = ""; };
37 | /* End PBXFileReference section */
38 |
39 | /* Begin PBXFrameworksBuildPhase section */
40 | 726FB40125BD612F007BEAE9 /* Frameworks */ = {
41 | isa = PBXFrameworksBuildPhase;
42 | buildActionMask = 2147483647;
43 | files = (
44 | );
45 | runOnlyForDeploymentPostprocessing = 0;
46 | };
47 | /* End PBXFrameworksBuildPhase section */
48 |
49 | /* Begin PBXGroup section */
50 | 726FB3FB25BD612F007BEAE9 = {
51 | isa = PBXGroup;
52 | children = (
53 | 726FB40625BD612F007BEAE9 /* Katip */,
54 | 726FB40525BD612F007BEAE9 /* Products */,
55 | );
56 | sourceTree = "";
57 | };
58 | 726FB40525BD612F007BEAE9 /* Products */ = {
59 | isa = PBXGroup;
60 | children = (
61 | 726FB40425BD612F007BEAE9 /* Katip.app */,
62 | );
63 | name = Products;
64 | sourceTree = "";
65 | };
66 | 726FB40625BD612F007BEAE9 /* Katip */ = {
67 | isa = PBXGroup;
68 | children = (
69 | 726FB40725BD612F007BEAE9 /* AppDelegate.swift */,
70 | 726FB41B25BD640E007BEAE9 /* ISOKatip.swift */,
71 | 727021A025EAB3AE0008200D /* ISOKatip+NSTableViewDataSource.swift */,
72 | 727021A325EAB4F10008200D /* ISOKatip+NSTableViewDelegate.swift */,
73 | 726FB41E25BD6975007BEAE9 /* ISOTranscriber.swift */,
74 | 726FB42825BD7FC0007BEAE9 /* Localization.strings */,
75 | 726FB40925BD6130007BEAE9 /* Assets.xcassets */,
76 | 726FB40B25BD6130007BEAE9 /* MainMenu.xib */,
77 | 726FB40E25BD6130007BEAE9 /* Info.plist */,
78 | 7270219C25EA9BD70008200D /* InfoPlist.strings */,
79 | 726FB40F25BD6130007BEAE9 /* Katip.entitlements */,
80 | );
81 | path = Katip;
82 | sourceTree = "";
83 | };
84 | /* End PBXGroup section */
85 |
86 | /* Begin PBXNativeTarget section */
87 | 726FB40325BD612F007BEAE9 /* Katip */ = {
88 | isa = PBXNativeTarget;
89 | buildConfigurationList = 726FB41225BD6130007BEAE9 /* Build configuration list for PBXNativeTarget "Katip" */;
90 | buildPhases = (
91 | 726FB40025BD612F007BEAE9 /* Sources */,
92 | 726FB40125BD612F007BEAE9 /* Frameworks */,
93 | 726FB40225BD612F007BEAE9 /* Resources */,
94 | 726FB42B25BDD736007BEAE9 /* ShellScript */,
95 | );
96 | buildRules = (
97 | );
98 | dependencies = (
99 | );
100 | name = Katip;
101 | productName = Katip;
102 | productReference = 726FB40425BD612F007BEAE9 /* Katip.app */;
103 | productType = "com.apple.product-type.application";
104 | };
105 | /* End PBXNativeTarget section */
106 |
107 | /* Begin PBXProject section */
108 | 726FB3FC25BD612F007BEAE9 /* Project object */ = {
109 | isa = PBXProject;
110 | attributes = {
111 | LastSwiftUpdateCheck = 1230;
112 | LastUpgradeCheck = 1240;
113 | TargetAttributes = {
114 | 726FB40325BD612F007BEAE9 = {
115 | CreatedOnToolsVersion = 12.3;
116 | };
117 | };
118 | };
119 | buildConfigurationList = 726FB3FF25BD612F007BEAE9 /* Build configuration list for PBXProject "Katip" */;
120 | compatibilityVersion = "Xcode 9.3";
121 | developmentRegion = en;
122 | hasScannedForEncodings = 0;
123 | knownRegions = (
124 | en,
125 | Base,
126 | de,
127 | );
128 | mainGroup = 726FB3FB25BD612F007BEAE9;
129 | productRefGroup = 726FB40525BD612F007BEAE9 /* Products */;
130 | projectDirPath = "";
131 | projectRoot = "";
132 | targets = (
133 | 726FB40325BD612F007BEAE9 /* Katip */,
134 | );
135 | };
136 | /* End PBXProject section */
137 |
138 | /* Begin PBXResourcesBuildPhase section */
139 | 726FB40225BD612F007BEAE9 /* Resources */ = {
140 | isa = PBXResourcesBuildPhase;
141 | buildActionMask = 2147483647;
142 | files = (
143 | 726FB42625BD7FC0007BEAE9 /* Localization.strings in Resources */,
144 | 7270219A25EA9BD70008200D /* InfoPlist.strings in Resources */,
145 | 726FB40A25BD6130007BEAE9 /* Assets.xcassets in Resources */,
146 | 726FB40D25BD6130007BEAE9 /* MainMenu.xib in Resources */,
147 | );
148 | runOnlyForDeploymentPostprocessing = 0;
149 | };
150 | /* End PBXResourcesBuildPhase section */
151 |
152 | /* Begin PBXShellScriptBuildPhase section */
153 | 726FB42B25BDD736007BEAE9 /* ShellScript */ = {
154 | isa = PBXShellScriptBuildPhase;
155 | buildActionMask = 2147483647;
156 | files = (
157 | );
158 | inputFileListPaths = (
159 | );
160 | inputPaths = (
161 | );
162 | outputFileListPaths = (
163 | );
164 | outputPaths = (
165 | );
166 | runOnlyForDeploymentPostprocessing = 0;
167 | shellPath = /bin/sh;
168 | shellScript = "buildNumber=$(/usr/libexec/PlistBuddy -c \"Print CFBundleVersion\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\")\nbuildNumber=$(($buildNumber + 1))\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $buildNumber\" \"${PROJECT_DIR}/${INFOPLIST_FILE}\"\n";
169 | };
170 | /* End PBXShellScriptBuildPhase section */
171 |
172 | /* Begin PBXSourcesBuildPhase section */
173 | 726FB40025BD612F007BEAE9 /* Sources */ = {
174 | isa = PBXSourcesBuildPhase;
175 | buildActionMask = 2147483647;
176 | files = (
177 | 726FB41F25BD6975007BEAE9 /* ISOTranscriber.swift in Sources */,
178 | 727021A125EAB3AE0008200D /* ISOKatip+NSTableViewDataSource.swift in Sources */,
179 | 727021A425EAB4F10008200D /* ISOKatip+NSTableViewDelegate.swift in Sources */,
180 | 726FB40825BD612F007BEAE9 /* AppDelegate.swift in Sources */,
181 | 726FB41C25BD640E007BEAE9 /* ISOKatip.swift in Sources */,
182 | );
183 | runOnlyForDeploymentPostprocessing = 0;
184 | };
185 | /* End PBXSourcesBuildPhase section */
186 |
187 | /* Begin PBXVariantGroup section */
188 | 726FB40B25BD6130007BEAE9 /* MainMenu.xib */ = {
189 | isa = PBXVariantGroup;
190 | children = (
191 | 726FB40C25BD6130007BEAE9 /* Base */,
192 | 7270219425EA9A7A0008200D /* de */,
193 | );
194 | name = MainMenu.xib;
195 | sourceTree = "";
196 | };
197 | 726FB42825BD7FC0007BEAE9 /* Localization.strings */ = {
198 | isa = PBXVariantGroup;
199 | children = (
200 | 726FB42725BD7FC0007BEAE9 /* en */,
201 | 7270219525EA9A7A0008200D /* de */,
202 | );
203 | name = Localization.strings;
204 | sourceTree = "";
205 | };
206 | 7270219C25EA9BD70008200D /* InfoPlist.strings */ = {
207 | isa = PBXVariantGroup;
208 | children = (
209 | 7270219B25EA9BD70008200D /* en */,
210 | 7270219E25EA9BE00008200D /* de */,
211 | );
212 | name = InfoPlist.strings;
213 | sourceTree = "";
214 | };
215 | /* End PBXVariantGroup section */
216 |
217 | /* Begin XCBuildConfiguration section */
218 | 726FB41025BD6130007BEAE9 /* Debug */ = {
219 | isa = XCBuildConfiguration;
220 | buildSettings = {
221 | ALWAYS_SEARCH_USER_PATHS = NO;
222 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
223 | CLANG_ANALYZER_NONNULL = YES;
224 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
225 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
226 | CLANG_CXX_LIBRARY = "libc++";
227 | CLANG_ENABLE_MODULES = YES;
228 | CLANG_ENABLE_OBJC_ARC = YES;
229 | CLANG_ENABLE_OBJC_WEAK = YES;
230 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
231 | CLANG_WARN_BOOL_CONVERSION = YES;
232 | CLANG_WARN_COMMA = YES;
233 | CLANG_WARN_CONSTANT_CONVERSION = YES;
234 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
235 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
236 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
237 | CLANG_WARN_EMPTY_BODY = YES;
238 | CLANG_WARN_ENUM_CONVERSION = YES;
239 | CLANG_WARN_INFINITE_RECURSION = YES;
240 | CLANG_WARN_INT_CONVERSION = YES;
241 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
242 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
243 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
244 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
245 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
246 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
247 | CLANG_WARN_STRICT_PROTOTYPES = YES;
248 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
249 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
250 | CLANG_WARN_UNREACHABLE_CODE = YES;
251 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
252 | COPY_PHASE_STRIP = NO;
253 | DEBUG_INFORMATION_FORMAT = dwarf;
254 | ENABLE_STRICT_OBJC_MSGSEND = YES;
255 | ENABLE_TESTABILITY = YES;
256 | GCC_C_LANGUAGE_STANDARD = gnu11;
257 | GCC_DYNAMIC_NO_PIC = NO;
258 | GCC_NO_COMMON_BLOCKS = YES;
259 | GCC_OPTIMIZATION_LEVEL = 0;
260 | GCC_PREPROCESSOR_DEFINITIONS = (
261 | "DEBUG=1",
262 | "$(inherited)",
263 | );
264 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
265 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
266 | GCC_WARN_UNDECLARED_SELECTOR = YES;
267 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
268 | GCC_WARN_UNUSED_FUNCTION = YES;
269 | GCC_WARN_UNUSED_VARIABLE = YES;
270 | MACOSX_DEPLOYMENT_TARGET = 10.15;
271 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
272 | MTL_FAST_MATH = YES;
273 | ONLY_ACTIVE_ARCH = YES;
274 | SDKROOT = macosx;
275 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
276 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
277 | };
278 | name = Debug;
279 | };
280 | 726FB41125BD6130007BEAE9 /* Release */ = {
281 | isa = XCBuildConfiguration;
282 | buildSettings = {
283 | ALWAYS_SEARCH_USER_PATHS = NO;
284 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
285 | CLANG_ANALYZER_NONNULL = YES;
286 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
287 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
288 | CLANG_CXX_LIBRARY = "libc++";
289 | CLANG_ENABLE_MODULES = YES;
290 | CLANG_ENABLE_OBJC_ARC = YES;
291 | CLANG_ENABLE_OBJC_WEAK = YES;
292 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
293 | CLANG_WARN_BOOL_CONVERSION = YES;
294 | CLANG_WARN_COMMA = YES;
295 | CLANG_WARN_CONSTANT_CONVERSION = YES;
296 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
297 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
298 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
299 | CLANG_WARN_EMPTY_BODY = YES;
300 | CLANG_WARN_ENUM_CONVERSION = YES;
301 | CLANG_WARN_INFINITE_RECURSION = YES;
302 | CLANG_WARN_INT_CONVERSION = YES;
303 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
304 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
305 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
306 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
307 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
308 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
309 | CLANG_WARN_STRICT_PROTOTYPES = YES;
310 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
311 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
312 | CLANG_WARN_UNREACHABLE_CODE = YES;
313 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
314 | COPY_PHASE_STRIP = NO;
315 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
316 | ENABLE_NS_ASSERTIONS = NO;
317 | ENABLE_STRICT_OBJC_MSGSEND = YES;
318 | GCC_C_LANGUAGE_STANDARD = gnu11;
319 | GCC_NO_COMMON_BLOCKS = YES;
320 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
321 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
322 | GCC_WARN_UNDECLARED_SELECTOR = YES;
323 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
324 | GCC_WARN_UNUSED_FUNCTION = YES;
325 | GCC_WARN_UNUSED_VARIABLE = YES;
326 | MACOSX_DEPLOYMENT_TARGET = 10.15;
327 | MTL_ENABLE_DEBUG_INFO = NO;
328 | MTL_FAST_MATH = YES;
329 | SDKROOT = macosx;
330 | SWIFT_COMPILATION_MODE = wholemodule;
331 | SWIFT_OPTIMIZATION_LEVEL = "-O";
332 | };
333 | name = Release;
334 | };
335 | 726FB41325BD6130007BEAE9 /* Debug */ = {
336 | isa = XCBuildConfiguration;
337 | buildSettings = {
338 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
339 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
340 | CODE_SIGN_ENTITLEMENTS = Katip/Katip.entitlements;
341 | CODE_SIGN_IDENTITY = "-";
342 | CODE_SIGN_STYLE = Automatic;
343 | COMBINE_HIDPI_IMAGES = YES;
344 | CURRENT_PROJECT_VERSION = 100;
345 | DEVELOPMENT_TEAM = RVR78FQQ62;
346 | ENABLE_HARDENED_RUNTIME = YES;
347 | INFOPLIST_FILE = Katip/Info.plist;
348 | LD_RUNPATH_SEARCH_PATHS = (
349 | "$(inherited)",
350 | "@executable_path/../Frameworks",
351 | );
352 | MARKETING_VERSION = 1.2.1;
353 | PRODUCT_BUNDLE_IDENTIFIER = de.imdat.Katip;
354 | PRODUCT_NAME = "$(TARGET_NAME)";
355 | SWIFT_VERSION = 5.0;
356 | };
357 | name = Debug;
358 | };
359 | 726FB41425BD6130007BEAE9 /* Release */ = {
360 | isa = XCBuildConfiguration;
361 | buildSettings = {
362 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
363 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
364 | CODE_SIGN_ENTITLEMENTS = Katip/Katip.entitlements;
365 | CODE_SIGN_IDENTITY = "-";
366 | CODE_SIGN_STYLE = Automatic;
367 | COMBINE_HIDPI_IMAGES = YES;
368 | CURRENT_PROJECT_VERSION = 100;
369 | DEVELOPMENT_TEAM = RVR78FQQ62;
370 | ENABLE_HARDENED_RUNTIME = YES;
371 | INFOPLIST_FILE = Katip/Info.plist;
372 | LD_RUNPATH_SEARCH_PATHS = (
373 | "$(inherited)",
374 | "@executable_path/../Frameworks",
375 | );
376 | MARKETING_VERSION = 1.2.1;
377 | PRODUCT_BUNDLE_IDENTIFIER = de.imdat.Katip;
378 | PRODUCT_NAME = "$(TARGET_NAME)";
379 | SWIFT_VERSION = 5.0;
380 | };
381 | name = Release;
382 | };
383 | /* End XCBuildConfiguration section */
384 |
385 | /* Begin XCConfigurationList section */
386 | 726FB3FF25BD612F007BEAE9 /* Build configuration list for PBXProject "Katip" */ = {
387 | isa = XCConfigurationList;
388 | buildConfigurations = (
389 | 726FB41025BD6130007BEAE9 /* Debug */,
390 | 726FB41125BD6130007BEAE9 /* Release */,
391 | );
392 | defaultConfigurationIsVisible = 0;
393 | defaultConfigurationName = Release;
394 | };
395 | 726FB41225BD6130007BEAE9 /* Build configuration list for PBXNativeTarget "Katip" */ = {
396 | isa = XCConfigurationList;
397 | buildConfigurations = (
398 | 726FB41325BD6130007BEAE9 /* Debug */,
399 | 726FB41425BD6130007BEAE9 /* Release */,
400 | );
401 | defaultConfigurationIsVisible = 0;
402 | defaultConfigurationName = Release;
403 | };
404 | /* End XCConfigurationList section */
405 | };
406 | rootObject = 726FB3FC25BD612F007BEAE9 /* Project object */;
407 | }
408 |
--------------------------------------------------------------------------------
/Katip/ISOKatip.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ISOKatip.swift
3 | // Katip
4 | //
5 | // Created by Imdat Solak on 24.01.21.
6 | //
7 |
8 | import Foundation
9 | import AppKit
10 | import AVFoundation
11 | import UserNotifications
12 | import Speech
13 |
14 | class ISOKatip: NSObject, ISOTranscriberDelegate, NSWindowDelegate {
15 | var transcriber: ISOTranscriber?
16 | private var recordingFileUrl: URL?
17 |
18 | @IBOutlet var mainWindow: NSWindow?
19 | @IBOutlet var outputTextView: NSTextView?
20 |
21 | @IBOutlet var languagesLabel: NSTextField?
22 | @IBOutlet var languagesPopup: NSPopUpButton!
23 |
24 | @IBOutlet var selectRecordingFileLabel: NSTextField?
25 | @IBOutlet var filenameField: NSTextField?
26 | @IBOutlet var openButton: NSButton?
27 | @IBOutlet var progressView: NSView?
28 | @IBOutlet var progressViewTitle: NSTextField?
29 | @IBOutlet var progressETALabel: NSTextField?
30 | @IBOutlet var stopButton: NSButton?
31 | @IBOutlet var retryButton: NSButton?
32 | @IBOutlet var saveButton: NSButton?
33 |
34 | @IBOutlet var preferencesMenuItem: NSMenuItem?
35 | @IBOutlet var preferencesPanel: NSPanel?
36 | @IBOutlet var preferencesLanguagesTable: NSTableView?
37 | @IBOutlet var preferencesIncludeTimestampsSwitch: NSSwitch?
38 |
39 | @IBOutlet var progressIndicator: NSProgressIndicator?
40 | private var transcribedSentences: [String] = []
41 |
42 | private var stopTranscription: Bool = false
43 | private var transcriptionWasStopped: Bool = false
44 |
45 | private var lastUsedLanguage: String?
46 | private var audioRecordingDuration = 0.0
47 | private var transcriptionTimeDivider = 1.0
48 | private var expectedTranscriptionDuration = 0.0
49 | private var transcriptionStartDate: Date?
50 | private var haveTranscriptionDivider = false
51 | private var displayLocales: [Locale] = []
52 | var storedLocales: [String] = []
53 |
54 | override
55 | init() {
56 | super.init()
57 | transcriber = ISOTranscriber()
58 | transcriber?.delegate = self
59 | lastUsedLanguage = UserDefaults.standard.string(forKey: "LastUsedLanguage")
60 | let lastTranscriptionDivider = UserDefaults.standard.double(forKey: "TranscriptionTimeDivider")
61 | if lastTranscriptionDivider > 0.0 {
62 | haveTranscriptionDivider = true
63 | transcriptionTimeDivider = lastTranscriptionDivider
64 | }
65 | storedLocales = UserDefaults.standard.stringArray(forKey: "SelectedLanguages") ?? []
66 | transcriber?.setup()
67 | }
68 |
69 | @IBAction private func help(_ : Any) {
70 | let url = URL(string: "https://github.com/imdatsolak/katip/blob/main/README.md")!
71 | if NSWorkspace.shared.open(url) {
72 | print("default browser was successfully opened")
73 | }
74 | }
75 |
76 | @IBAction private func openFile(_ : Any) {
77 | let openPanel = NSOpenPanel()
78 | self.recordingFileUrl = nil
79 | openPanel.canChooseFiles = true
80 | openPanel.canChooseDirectories = false
81 | openPanel.allowsMultipleSelection = false
82 | openPanel.title = NSLocalizedString("openpanel.title", tableName: "Localization", value: "openpanel.title", comment: "")
83 | openPanel.prompt = NSLocalizedString("openpanel.prompt", tableName: "Localization", value: "openpanel.prompt", comment: "")
84 | openPanel.message = NSLocalizedString("openpanel.message", tableName: "Localization", value: "openpanel.message", comment: "")
85 | openPanel.allowedFileTypes = ["m4a", "mp3", "wav", "mpa"]
86 | if openPanel.runModal() == NSApplication.ModalResponse.OK {
87 | if let recordingFileUrl = openPanel.url, let mainWindow = self.mainWindow {
88 | let alert = NSAlert()
89 | alert.messageText = NSLocalizedString("transcription.info.dialog.title", tableName: "Localization", value: "transcription.info.dialog.title", comment: "")
90 | alert.informativeText = NSLocalizedString("transcription.info.dialog.message", tableName: "Localization", value: "transcription.info.dialog.message", comment: "")
91 | alert.alertStyle = NSAlert.Style.informational
92 | alert.beginSheetModal(for: mainWindow) { _ in
93 | self.transcriptionStartDate = Date()
94 | self.startTranscription(recordingFileUrl)
95 | }
96 | }
97 | }
98 | }
99 |
100 | private func prepareTranscription(_ url: URL) {
101 | self.recordingFileUrl = url
102 | mainWindow?.makeKeyAndOrderFront(self)
103 | saveButton?.isEnabled = false
104 | retryButton?.isEnabled = false
105 | openButton?.isEnabled = false
106 | languagesPopup?.isEnabled = false
107 | progressView?.isHidden = false
108 | progressView?.layer?.backgroundColor = NSColor.black.withAlphaComponent(0.80).cgColor
109 | progressView?.layer?.cornerRadius = 10
110 | progressView?.layer?.masksToBounds = true
111 | progressIndicator?.startAnimation(self)
112 | transcribedSentences = []
113 | filenameField?.stringValue = url.relativePath
114 | stopTranscription = false
115 | mainWindow?.title = "Katip - " + String(format: NSLocalizedString("message.transcribing", tableName: "Localization", value: "message.transcribing", comment: ""), self.recordingFileUrl?.lastPathComponent ?? "")
116 | let asset = AVURLAsset(url: url, options: nil)
117 | let audioDuration = asset.duration
118 | audioRecordingDuration = CMTimeGetSeconds(audioDuration)
119 | expectedTranscriptionDuration = audioRecordingDuration * transcriptionTimeDivider
120 | progressIndicator?.isIndeterminate = !haveTranscriptionDivider
121 | if haveTranscriptionDivider {
122 | progressIndicator?.minValue = 0.0
123 | progressIndicator?.maxValue = expectedTranscriptionDuration
124 | progressIndicator?.doubleValue = 0.0
125 | }
126 | updateTranscriptionETA()
127 | refreshOutputTextView()
128 | }
129 |
130 | private func startTranscription(_ url: URL) {
131 | lastUsedLanguage = self.displayLocales[self.languagesPopup.indexOfSelectedItem].identifier
132 | if let lastUsedLanguage = lastUsedLanguage {
133 | UserDefaults.standard.set(lastUsedLanguage, forKey: "LastUsedLanguage")
134 | preferencesPanel?.orderOut(self)
135 | preferencesMenuItem?.isEnabled = false
136 | prepareTranscription(url)
137 | self.transcriptionStartDate = Date()
138 | self.transcriber?.startTranscription(url, usingLanguage: lastUsedLanguage)
139 | }
140 | }
141 |
142 | @IBAction private func stopTranscription(_ : Any) {
143 | stopTranscription = true
144 | progressETALabel?.stringValue = NSLocalizedString("message.will.stop", tableName: "Localization", value: "message.will.stop", comment: "")
145 | mainWindow?.title = NSLocalizedString("window.title.stopping", tableName: "Localization", value: "window.title.stopping", comment: "")
146 | transcriptionWasStopped = true
147 | }
148 |
149 | @IBAction private func retryTranscription(_ : Any) {
150 | if let url = self.recordingFileUrl {
151 | startTranscription(url)
152 | }
153 | }
154 |
155 | @IBAction private func saveTranscription(_ : Any) {
156 | let savePanel = NSSavePanel()
157 | savePanel.title = NSLocalizedString("savepanel.title", tableName: "Localization", value: "savepanel.title", comment: "")
158 | savePanel.prompt = NSLocalizedString("savepanel.prompt", tableName: "Localization", value: "savepanel.prompt", comment: "")
159 | savePanel.message = NSLocalizedString("savepanel.message", tableName: "Localization", value: "savepanel.message", comment: "")
160 | savePanel.nameFieldStringValue = (self.recordingFileUrl?.deletingPathExtension().lastPathComponent ?? NSLocalizedString("savepanel.untitle", tableName: "Localization", value: "savepanel.untitle", comment: "")) + ".txt"
161 | if let mainWindow = self.mainWindow {
162 | savePanel.beginSheetModal(for: mainWindow) { response in
163 | if response == NSApplication.ModalResponse.OK, let fileURL = savePanel.url {
164 | var text: String = ""
165 | for entry in self.transcribedSentences {
166 | text += entry + ".\n\n"
167 | }
168 | do {
169 | try text.write(to: fileURL, atomically: true, encoding: String.Encoding.utf8)
170 | } catch {
171 | let alert = NSAlert()
172 | alert.messageText = NSLocalizedString("saveerror.dialog.title", tableName: "Localization", value: "saveerror.dialog.title", comment: "")
173 | alert.informativeText = String(format: NSLocalizedString("saveerror.dialog.message", tableName: "Localization", value: "saveerror.dialog.message", comment: ""), self.recordingFileUrl?.relativePath ?? "")
174 | alert.alertStyle = NSAlert.Style.critical
175 | alert.beginSheetModal(for: mainWindow) { _ in
176 | }
177 | }
178 | }
179 | }
180 | }
181 | }
182 |
183 | private func refreshOutputTextView() {
184 | var text: String = ""
185 | for entry in transcribedSentences {
186 | text += entry + ".\n\n"
187 | }
188 | if text == "" {
189 | text = NSLocalizedString("message.waiting", tableName: "Localization", value: "message.waiting", comment: "")
190 | }
191 | outputTextView?.string = text
192 | if abs(self.outputTextView?.visibleRect.maxY ?? 100) - (self.outputTextView?.bounds.maxY ?? 100) < 25.0 {
193 | self.outputTextView?.scrollRangeToVisible(NSRange(location: text.count, length: 0))
194 | }
195 | }
196 |
197 | func updateTranscriptionETA() {
198 | var remainingTime = expectedTranscriptionDuration
199 | if let tsd = transcriptionStartDate {
200 | let difference = Date().timeIntervalSince(tsd)
201 | remainingTime -= difference
202 | }
203 | if progressIndicator?.isIndeterminate == false {
204 | progressIndicator?.doubleValue = expectedTranscriptionDuration - remainingTime
205 | }
206 | var eta = remainingTime
207 | let hours = Int((eta / 60 / 60))
208 | eta -= (Double(hours) * 60.0 * 60.0)
209 | let minutes = Int(eta / 60)
210 | eta -= (Double(minutes) * 60.0)
211 | let seconds = Int(eta)
212 | progressETALabel?.stringValue = String(format: NSLocalizedString("message.eta", tableName: "Localization", value: "message.eta", comment: ""), hours, minutes, seconds )
213 | }
214 |
215 | private func resetUI() {
216 | saveButton?.isEnabled = true
217 | retryButton?.isEnabled = true
218 | openButton?.isEnabled = true
219 | languagesPopup?.isEnabled = true
220 | progressIndicator?.stopAnimation(self)
221 | progressView?.isHidden = true
222 | mainWindow?.title = "Katip"
223 | preferencesMenuItem?.isEnabled = true
224 | }
225 |
226 | func updateLanguagesPopup() {
227 | var selectedLocaleIndex: Int = -1
228 | self.languagesPopup.removeAllItems()
229 | var localeIndex: Int = 0
230 | for locale in displayLocales {
231 | self.languagesPopup.addItem(withTitle: localizedNameForLocale(locale))
232 | if lastUsedLanguage != nil, locale.identifier == lastUsedLanguage {
233 | selectedLocaleIndex = localeIndex
234 | } else if locale.identifier == Locale.current.identifier {
235 | selectedLocaleIndex = localeIndex
236 | } else if selectedLocaleIndex == -1, locale.identifier.prefix(2) == Locale.current.identifier.prefix(2) {
237 | selectedLocaleIndex = localeIndex
238 | }
239 | localeIndex += 1
240 | }
241 | self.languagesPopup.displayIfNeeded()
242 | if selectedLocaleIndex >= 0 {
243 | self.languagesPopup.selectItem(at: selectedLocaleIndex)
244 | }
245 | }
246 |
247 | func localizedNameForLocale(_ locale: Locale) -> String {
248 | if let lCode = locale.languageCode, let languageCode = locale.localizedString(forLanguageCode: lCode), let rCode = locale.regionCode, let regionCode = locale.localizedString(forRegionCode: rCode) {
249 | return languageCode.localizedCapitalized + " (" + regionCode + ")"
250 | } else if let languageCode = locale.localizedString(forIdentifier: locale.identifier) {
251 | return languageCode.localizedCapitalized
252 | } else {
253 | return locale.identifier
254 | }
255 | }
256 |
257 | func prepareDisplayLocales() {
258 | displayLocales.removeAll()
259 | if let transcriber = self.transcriber, transcriber.recognitionEnabled {
260 | for locale in transcriber.supportedLocales {
261 | displayLocales.append(locale)
262 | }
263 | }
264 | if displayLocales.count > 0, storedLocales.count > 0 {
265 | let localList = displayLocales
266 | displayLocales.removeAll()
267 | for locale in localList {
268 | if storedLocales.contains(locale.identifier) {
269 | displayLocales.append(locale)
270 | }
271 | }
272 | }
273 | }
274 |
275 | // MARK: Speech recognition Delegate
276 | func shouldStopTranscription() -> Bool {
277 | // We need to reset the value after returning, as we might be called twice
278 | // and that might create a problem in ISOTranscriber
279 | let retVal = stopTranscription
280 | stopTranscription = false
281 | return retVal
282 | }
283 |
284 | func secondsToHoursMinutesSeconds (seconds : Int) -> (Int, Int, Int) {
285 | return (seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60)
286 | }
287 |
288 | func secondsAsTime(_ seconds: Double) -> String {
289 | let hS: Int = Int(seconds)
290 | let (h, m, s) = secondsToHoursMinutesSeconds(seconds: hS)
291 | return String(format:"%02d:%02d:%02d", h, m, s)
292 | }
293 |
294 | func utterancesWithDetails(fromTranscription transcription: SFTranscription, withText text: String) -> String {
295 | if let firstSegment = transcription.segments.first, let lastSegment = transcription.segments.last {
296 | let x = secondsAsTime(firstSegment.timestamp)
297 | let y = secondsAsTime(lastSegment.timestamp + lastSegment.duration)
298 | return "[\(x) - \(y)] \(text)"
299 | }
300 | return text
301 | }
302 |
303 | func setTranscribedText(_ text: String, withSentenceNo sentenceNo: Int, fromTranscription transcription: SFTranscription) {
304 | updateTranscriptionETA()
305 | var transcribedText = ""
306 | if UserDefaults.standard.bool(forKey: "ShouldIncludeTimeStampForUtterances") {
307 | transcribedText = utterancesWithDetails(fromTranscription: transcription, withText: text)
308 | } else {
309 | transcribedText = text
310 | }
311 | if sentenceNo == transcribedSentences.count {
312 | transcribedSentences.append(transcribedText)
313 | } else if sentenceNo < transcribedSentences.count {
314 | transcribedSentences[sentenceNo] = transcribedText
315 | }
316 | refreshOutputTextView()
317 | }
318 |
319 | func transcriptionDone() {
320 | resetUI()
321 | if transcriptionWasStopped == false {
322 | if let tsd = transcriptionStartDate, audioRecordingDuration > 0.0 {
323 | let actualTime = Date().timeIntervalSince(tsd)
324 | let newDividier = actualTime / audioRecordingDuration
325 | transcriptionTimeDivider = (transcriptionTimeDivider + newDividier) / 2
326 | UserDefaults.standard.setValue(transcriptionTimeDivider, forKey: "TranscriptionTimeDivider")
327 | transcriptionStartDate = nil
328 | haveTranscriptionDivider = true
329 | }
330 | }
331 | if let appDelegate = NSApplication.shared.delegate as? AppDelegate, appDelegate.isActive, let mainWindow = self.mainWindow {
332 | if transcriptionWasStopped == false {
333 | let alert = NSAlert()
334 | alert.messageText = NSLocalizedString("transcription.done.notification.title", tableName: "Localization", value: "transcription.done.notification.title", comment: "")
335 | alert.informativeText = String(format: NSLocalizedString("transcription.done.notification.message", tableName: "Localization", value: "transcription.done.notification.message", comment: ""), self.recordingFileUrl?.lastPathComponent ?? "")
336 | alert.alertStyle = NSAlert.Style.informational
337 | alert.beginSheetModal(for: mainWindow) { _ in
338 | }
339 | }
340 | } else {
341 | let content = UNMutableNotificationContent()
342 | content.title = NSLocalizedString("transcription.done.notification.title", tableName: "Localization", value: "transcription.done.notification.title", comment: "")
343 | content.subtitle = String(format: NSLocalizedString("transcription.done.notification.message", tableName: "Localization", value: "transcription.done.notification.message", comment: ""), self.recordingFileUrl?.lastPathComponent ?? "")
344 | content.sound = UNNotificationSound.default
345 | let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
346 | UNUserNotificationCenter.current().add(request)
347 | }
348 | transcriptionWasStopped = false
349 | }
350 |
351 | func transcriptionCanceled() {
352 | resetUI()
353 | }
354 | func speechRecognitionAuthorized() {
355 | prepareDisplayLocales()
356 | updateLanguagesPopup()
357 | }
358 |
359 | func speechRecognitionAuthorizationFailed(_ error: String) {
360 | }
361 |
362 | // MARK: Preferences
363 | @IBAction private func showPreferences(_ : Any) {
364 | var selectedLanguagesSet = IndexSet()
365 | if let mainWindow = mainWindow, let preferencesPanel = preferencesPanel {
366 | preferencesLanguagesTable?.delegate = self
367 | preferencesLanguagesTable?.dataSource = self
368 | if let transcriber = self.transcriber, transcriber.recognitionEnabled {
369 | for i in 0..
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
376 |
390 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
641 |
642 |
643 |
644 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
672 |
680 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
--------------------------------------------------------------------------------