├── .gitignore ├── Kyapchar.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Kyapchar ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png │ ├── Contents.json │ ├── pause.imageset │ │ ├── Contents.json │ │ └── pause.png │ ├── record.imageset │ │ ├── Contents.json │ │ └── record.png │ ├── resume.imageset │ │ ├── Contents.json │ │ └── resume.png │ ├── stop-1.imageset │ │ ├── Contents.json │ │ └── stop-1.png │ └── stop-2.imageset │ │ ├── Contents.json │ │ └── stop-2.png ├── Base.lproj │ └── MainMenu.xib ├── Info.plist ├── LaunchAtLoginHelper.swift ├── MenuController.swift ├── Preferences.swift ├── Preferences.xib └── Recorder.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ## Build generated 2 | build/ 3 | DerivedData/ 4 | 5 | ## Various settings 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | 16 | ## Other 17 | *.moved-aside 18 | *.xccheckout 19 | *.xcscmblueprint 20 | -------------------------------------------------------------------------------- /Kyapchar.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 08016E591EFD116A00409465 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 08016E581EFD116A00409465 /* Preferences.xib */; }; 11 | 0834CC341EFFEEE70037C1D2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0834CC331EFFEEE70037C1D2 /* Assets.xcassets */; }; 12 | 086724DC1DC86E07007033BB /* Recorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 086724DB1DC86E07007033BB /* Recorder.swift */; }; 13 | 088746CD1EFFC7A6007636BD /* LaunchAtLoginHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 088746CC1EFFC7A6007636BD /* LaunchAtLoginHelper.swift */; }; 14 | 088746D01EFFC92E007636BD /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 088746CF1EFFC92E007636BD /* Foundation.framework */; }; 15 | 089CD7551DC7B742005067DB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089CD7541DC7B742005067DB /* AppDelegate.swift */; }; 16 | 089CD75A1DC7B742005067DB /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 089CD7581DC7B742005067DB /* MainMenu.xib */; }; 17 | 089CD7641DC85A3C005067DB /* MenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089CD7631DC85A3C005067DB /* MenuController.swift */; }; 18 | 08CCC4B51EFD203B00521354 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 08CCC4B41EFD203B00521354 /* Preferences.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXCopyFilesBuildPhase section */ 22 | 08CCC4D01EFD444900521354 /* CopyFiles */ = { 23 | isa = PBXCopyFilesBuildPhase; 24 | buildActionMask = 12; 25 | dstPath = Contents/Library/LoginItems; 26 | dstSubfolderSpec = 1; 27 | files = ( 28 | ); 29 | runOnlyForDeploymentPostprocessing = 0; 30 | }; 31 | /* End PBXCopyFilesBuildPhase section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 08016E581EFD116A00409465 /* Preferences.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = Preferences.xib; sourceTree = ""; }; 35 | 0834CC331EFFEEE70037C1D2 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 36 | 086724DB1DC86E07007033BB /* Recorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Recorder.swift; sourceTree = ""; }; 37 | 088746CC1EFFC7A6007636BD /* LaunchAtLoginHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LaunchAtLoginHelper.swift; sourceTree = ""; }; 38 | 088746CF1EFFC92E007636BD /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 39 | 089CD7511DC7B742005067DB /* Kyapchar.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Kyapchar.app; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | 089CD7541DC7B742005067DB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 41 | 089CD7591DC7B742005067DB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 42 | 089CD75B1DC7B742005067DB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | 089CD7631DC85A3C005067DB /* MenuController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuController.swift; sourceTree = ""; }; 44 | 08CCC4B41EFD203B00521354 /* Preferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 089CD74E1DC7B742005067DB /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | 088746D01EFFC92E007636BD /* Foundation.framework in Frameworks */, 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | /* End PBXFrameworksBuildPhase section */ 57 | 58 | /* Begin PBXGroup section */ 59 | 088746CE1EFFC92E007636BD /* Frameworks */ = { 60 | isa = PBXGroup; 61 | children = ( 62 | 088746CF1EFFC92E007636BD /* Foundation.framework */, 63 | ); 64 | name = Frameworks; 65 | sourceTree = ""; 66 | }; 67 | 089CD7481DC7B742005067DB = { 68 | isa = PBXGroup; 69 | children = ( 70 | 089CD7531DC7B742005067DB /* Kyapchar */, 71 | 089CD7521DC7B742005067DB /* Products */, 72 | 088746CE1EFFC92E007636BD /* Frameworks */, 73 | ); 74 | sourceTree = ""; 75 | }; 76 | 089CD7521DC7B742005067DB /* Products */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 089CD7511DC7B742005067DB /* Kyapchar.app */, 80 | ); 81 | name = Products; 82 | sourceTree = ""; 83 | }; 84 | 089CD7531DC7B742005067DB /* Kyapchar */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 088746CC1EFFC7A6007636BD /* LaunchAtLoginHelper.swift */, 88 | 08CCC4B41EFD203B00521354 /* Preferences.swift */, 89 | 089CD7631DC85A3C005067DB /* MenuController.swift */, 90 | 086724DB1DC86E07007033BB /* Recorder.swift */, 91 | 089CD7541DC7B742005067DB /* AppDelegate.swift */, 92 | 089CD7581DC7B742005067DB /* MainMenu.xib */, 93 | 08016E581EFD116A00409465 /* Preferences.xib */, 94 | 089CD75B1DC7B742005067DB /* Info.plist */, 95 | 0834CC331EFFEEE70037C1D2 /* Assets.xcassets */, 96 | ); 97 | path = Kyapchar; 98 | sourceTree = ""; 99 | }; 100 | /* End PBXGroup section */ 101 | 102 | /* Begin PBXNativeTarget section */ 103 | 089CD7501DC7B742005067DB /* Kyapchar */ = { 104 | isa = PBXNativeTarget; 105 | buildConfigurationList = 089CD75E1DC7B742005067DB /* Build configuration list for PBXNativeTarget "Kyapchar" */; 106 | buildPhases = ( 107 | 089CD74D1DC7B742005067DB /* Sources */, 108 | 089CD74E1DC7B742005067DB /* Frameworks */, 109 | 089CD74F1DC7B742005067DB /* Resources */, 110 | 08CCC4D01EFD444900521354 /* CopyFiles */, 111 | ); 112 | buildRules = ( 113 | ); 114 | dependencies = ( 115 | ); 116 | name = Kyapchar; 117 | productName = Kyapchar; 118 | productReference = 089CD7511DC7B742005067DB /* Kyapchar.app */; 119 | productType = "com.apple.product-type.application"; 120 | }; 121 | /* End PBXNativeTarget section */ 122 | 123 | /* Begin PBXProject section */ 124 | 089CD7491DC7B742005067DB /* Project object */ = { 125 | isa = PBXProject; 126 | attributes = { 127 | LastSwiftUpdateCheck = 0830; 128 | LastUpgradeCheck = 0820; 129 | ORGANIZATIONNAME = "Vishal Telangre"; 130 | TargetAttributes = { 131 | 089CD7501DC7B742005067DB = { 132 | CreatedOnToolsVersion = 7.3.1; 133 | LastSwiftMigration = 0830; 134 | }; 135 | }; 136 | }; 137 | buildConfigurationList = 089CD74C1DC7B742005067DB /* Build configuration list for PBXProject "Kyapchar" */; 138 | compatibilityVersion = "Xcode 3.2"; 139 | developmentRegion = English; 140 | hasScannedForEncodings = 0; 141 | knownRegions = ( 142 | en, 143 | Base, 144 | ); 145 | mainGroup = 089CD7481DC7B742005067DB; 146 | productRefGroup = 089CD7521DC7B742005067DB /* Products */; 147 | projectDirPath = ""; 148 | projectRoot = ""; 149 | targets = ( 150 | 089CD7501DC7B742005067DB /* Kyapchar */, 151 | ); 152 | }; 153 | /* End PBXProject section */ 154 | 155 | /* Begin PBXResourcesBuildPhase section */ 156 | 089CD74F1DC7B742005067DB /* Resources */ = { 157 | isa = PBXResourcesBuildPhase; 158 | buildActionMask = 2147483647; 159 | files = ( 160 | 08016E591EFD116A00409465 /* Preferences.xib in Resources */, 161 | 0834CC341EFFEEE70037C1D2 /* Assets.xcassets in Resources */, 162 | 089CD75A1DC7B742005067DB /* MainMenu.xib in Resources */, 163 | ); 164 | runOnlyForDeploymentPostprocessing = 0; 165 | }; 166 | /* End PBXResourcesBuildPhase section */ 167 | 168 | /* Begin PBXSourcesBuildPhase section */ 169 | 089CD74D1DC7B742005067DB /* Sources */ = { 170 | isa = PBXSourcesBuildPhase; 171 | buildActionMask = 2147483647; 172 | files = ( 173 | 086724DC1DC86E07007033BB /* Recorder.swift in Sources */, 174 | 088746CD1EFFC7A6007636BD /* LaunchAtLoginHelper.swift in Sources */, 175 | 089CD7551DC7B742005067DB /* AppDelegate.swift in Sources */, 176 | 08CCC4B51EFD203B00521354 /* Preferences.swift in Sources */, 177 | 089CD7641DC85A3C005067DB /* MenuController.swift in Sources */, 178 | ); 179 | runOnlyForDeploymentPostprocessing = 0; 180 | }; 181 | /* End PBXSourcesBuildPhase section */ 182 | 183 | /* Begin PBXVariantGroup section */ 184 | 089CD7581DC7B742005067DB /* MainMenu.xib */ = { 185 | isa = PBXVariantGroup; 186 | children = ( 187 | 089CD7591DC7B742005067DB /* Base */, 188 | ); 189 | name = MainMenu.xib; 190 | sourceTree = ""; 191 | }; 192 | /* End PBXVariantGroup section */ 193 | 194 | /* Begin XCBuildConfiguration section */ 195 | 089CD75C1DC7B742005067DB /* Debug */ = { 196 | isa = XCBuildConfiguration; 197 | buildSettings = { 198 | ALWAYS_SEARCH_USER_PATHS = NO; 199 | ASSETCATALOG_COMPILER_APPICON_NAME = ""; 200 | CLANG_ANALYZER_NONNULL = YES; 201 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 202 | CLANG_CXX_LIBRARY = "libc++"; 203 | CLANG_ENABLE_MODULES = YES; 204 | CLANG_ENABLE_OBJC_ARC = YES; 205 | CLANG_WARN_BOOL_CONVERSION = YES; 206 | CLANG_WARN_CONSTANT_CONVERSION = YES; 207 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 208 | CLANG_WARN_EMPTY_BODY = YES; 209 | CLANG_WARN_ENUM_CONVERSION = YES; 210 | CLANG_WARN_INFINITE_RECURSION = YES; 211 | CLANG_WARN_INT_CONVERSION = YES; 212 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 213 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 214 | CLANG_WARN_UNREACHABLE_CODE = YES; 215 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 216 | CODE_SIGN_IDENTITY = "-"; 217 | COPY_PHASE_STRIP = NO; 218 | DEBUG_INFORMATION_FORMAT = dwarf; 219 | ENABLE_STRICT_OBJC_MSGSEND = YES; 220 | ENABLE_TESTABILITY = YES; 221 | GCC_C_LANGUAGE_STANDARD = gnu99; 222 | GCC_DYNAMIC_NO_PIC = NO; 223 | GCC_NO_COMMON_BLOCKS = YES; 224 | GCC_OPTIMIZATION_LEVEL = 0; 225 | GCC_PREPROCESSOR_DEFINITIONS = ( 226 | "DEBUG=1", 227 | "$(inherited)", 228 | ); 229 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 230 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 231 | GCC_WARN_UNDECLARED_SELECTOR = YES; 232 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 233 | GCC_WARN_UNUSED_FUNCTION = YES; 234 | GCC_WARN_UNUSED_VARIABLE = YES; 235 | MACOSX_DEPLOYMENT_TARGET = 10.11; 236 | MTL_ENABLE_DEBUG_INFO = YES; 237 | ONLY_ACTIVE_ARCH = YES; 238 | SDKROOT = macosx; 239 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 240 | }; 241 | name = Debug; 242 | }; 243 | 089CD75D1DC7B742005067DB /* Release */ = { 244 | isa = XCBuildConfiguration; 245 | buildSettings = { 246 | ALWAYS_SEARCH_USER_PATHS = NO; 247 | ASSETCATALOG_COMPILER_APPICON_NAME = ""; 248 | CLANG_ANALYZER_NONNULL = YES; 249 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 250 | CLANG_CXX_LIBRARY = "libc++"; 251 | CLANG_ENABLE_MODULES = YES; 252 | CLANG_ENABLE_OBJC_ARC = YES; 253 | CLANG_WARN_BOOL_CONVERSION = YES; 254 | CLANG_WARN_CONSTANT_CONVERSION = YES; 255 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 256 | CLANG_WARN_EMPTY_BODY = YES; 257 | CLANG_WARN_ENUM_CONVERSION = YES; 258 | CLANG_WARN_INFINITE_RECURSION = YES; 259 | CLANG_WARN_INT_CONVERSION = YES; 260 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 261 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 262 | CLANG_WARN_UNREACHABLE_CODE = YES; 263 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 264 | CODE_SIGN_IDENTITY = "-"; 265 | COPY_PHASE_STRIP = NO; 266 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 267 | ENABLE_NS_ASSERTIONS = NO; 268 | ENABLE_STRICT_OBJC_MSGSEND = YES; 269 | GCC_C_LANGUAGE_STANDARD = gnu99; 270 | GCC_NO_COMMON_BLOCKS = YES; 271 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 272 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 273 | GCC_WARN_UNDECLARED_SELECTOR = YES; 274 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 275 | GCC_WARN_UNUSED_FUNCTION = YES; 276 | GCC_WARN_UNUSED_VARIABLE = YES; 277 | MACOSX_DEPLOYMENT_TARGET = 10.11; 278 | MTL_ENABLE_DEBUG_INFO = NO; 279 | SDKROOT = macosx; 280 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 281 | }; 282 | name = Release; 283 | }; 284 | 089CD75F1DC7B742005067DB /* Debug */ = { 285 | isa = XCBuildConfiguration; 286 | buildSettings = { 287 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 288 | CLANG_ENABLE_MODULES = YES; 289 | COMBINE_HIDPI_IMAGES = YES; 290 | INFOPLIST_FILE = Kyapchar/Info.plist; 291 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 292 | PRODUCT_BUNDLE_IDENTIFIER = com.vishaltelangre.Kyapchar; 293 | PRODUCT_NAME = "$(TARGET_NAME)"; 294 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 295 | SWIFT_VERSION = 3.0; 296 | }; 297 | name = Debug; 298 | }; 299 | 089CD7601DC7B742005067DB /* Release */ = { 300 | isa = XCBuildConfiguration; 301 | buildSettings = { 302 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 303 | CLANG_ENABLE_MODULES = YES; 304 | COMBINE_HIDPI_IMAGES = YES; 305 | INFOPLIST_FILE = Kyapchar/Info.plist; 306 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 307 | PRODUCT_BUNDLE_IDENTIFIER = com.vishaltelangre.Kyapchar; 308 | PRODUCT_NAME = "$(TARGET_NAME)"; 309 | SWIFT_VERSION = 3.0; 310 | }; 311 | name = Release; 312 | }; 313 | /* End XCBuildConfiguration section */ 314 | 315 | /* Begin XCConfigurationList section */ 316 | 089CD74C1DC7B742005067DB /* Build configuration list for PBXProject "Kyapchar" */ = { 317 | isa = XCConfigurationList; 318 | buildConfigurations = ( 319 | 089CD75C1DC7B742005067DB /* Debug */, 320 | 089CD75D1DC7B742005067DB /* Release */, 321 | ); 322 | defaultConfigurationIsVisible = 0; 323 | defaultConfigurationName = Release; 324 | }; 325 | 089CD75E1DC7B742005067DB /* Build configuration list for PBXNativeTarget "Kyapchar" */ = { 326 | isa = XCConfigurationList; 327 | buildConfigurations = ( 328 | 089CD75F1DC7B742005067DB /* Debug */, 329 | 089CD7601DC7B742005067DB /* Release */, 330 | ); 331 | defaultConfigurationIsVisible = 0; 332 | defaultConfigurationName = Release; 333 | }; 334 | /* End XCConfigurationList section */ 335 | }; 336 | rootObject = 089CD7491DC7B742005067DB /* Project object */; 337 | } 338 | -------------------------------------------------------------------------------- /Kyapchar.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Kyapchar/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Kyapchar 4 | // 5 | // Created by Vishal Telangre on 10/31/16. 6 | // Copyright © 2016 Vishal Telangre. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | func applicationDidFinishLaunching(_ aNotification: Notification) { 15 | let configuredSaveLocation = UserDefaults.standard.url(forKey: "KyapcharSaveLocation") 16 | let defaultSaveLocation = FileManager.default.urls(for: .moviesDirectory, in: .userDomainMask).first 17 | 18 | if (configuredSaveLocation == nil) { 19 | UserDefaults.standard.set(defaultSaveLocation, forKey: "KyapcharSaveLocation") 20 | } 21 | } 22 | 23 | func applicationWillTerminate(_ aNotification: Notification) { 24 | // Insert code here to tear down your application 25 | } 26 | 27 | } 28 | 29 | -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "icon_16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "icon_16x16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "icon_32x32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "icon_32x32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "icon_128x128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "icon_128x128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "icon_256x256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "icon_256x256@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "icon_512x512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "icon_512x512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishaltelangre/Kyapchar/9520b0f072f26ebe2ba9a722786bab5d278aec41/Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishaltelangre/Kyapchar/9520b0f072f26ebe2ba9a722786bab5d278aec41/Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishaltelangre/Kyapchar/9520b0f072f26ebe2ba9a722786bab5d278aec41/Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishaltelangre/Kyapchar/9520b0f072f26ebe2ba9a722786bab5d278aec41/Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishaltelangre/Kyapchar/9520b0f072f26ebe2ba9a722786bab5d278aec41/Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishaltelangre/Kyapchar/9520b0f072f26ebe2ba9a722786bab5d278aec41/Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishaltelangre/Kyapchar/9520b0f072f26ebe2ba9a722786bab5d278aec41/Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishaltelangre/Kyapchar/9520b0f072f26ebe2ba9a722786bab5d278aec41/Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishaltelangre/Kyapchar/9520b0f072f26ebe2ba9a722786bab5d278aec41/Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishaltelangre/Kyapchar/9520b0f072f26ebe2ba9a722786bab5d278aec41/Kyapchar/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/pause.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "mac", 9 | "filename" : "pause.png", 10 | "scale" : "2x" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | }, 17 | "properties" : { 18 | "template-rendering-intent" : "template" 19 | } 20 | } -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/pause.imageset/pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishaltelangre/Kyapchar/9520b0f072f26ebe2ba9a722786bab5d278aec41/Kyapchar/Assets.xcassets/pause.imageset/pause.png -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/record.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "mac", 9 | "filename" : "record.png", 10 | "scale" : "2x" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | }, 17 | "properties" : { 18 | "template-rendering-intent" : "template" 19 | } 20 | } -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/record.imageset/record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishaltelangre/Kyapchar/9520b0f072f26ebe2ba9a722786bab5d278aec41/Kyapchar/Assets.xcassets/record.imageset/record.png -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/resume.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "mac", 9 | "filename" : "resume.png", 10 | "scale" : "2x" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | }, 17 | "properties" : { 18 | "template-rendering-intent" : "template" 19 | } 20 | } -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/resume.imageset/resume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishaltelangre/Kyapchar/9520b0f072f26ebe2ba9a722786bab5d278aec41/Kyapchar/Assets.xcassets/resume.imageset/resume.png -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/stop-1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "mac", 9 | "filename" : "stop-1.png", 10 | "scale" : "2x" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | }, 17 | "properties" : { 18 | "template-rendering-intent" : "template" 19 | } 20 | } -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/stop-1.imageset/stop-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishaltelangre/Kyapchar/9520b0f072f26ebe2ba9a722786bab5d278aec41/Kyapchar/Assets.xcassets/stop-1.imageset/stop-1.png -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/stop-2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "mac", 9 | "filename" : "stop-2.png", 10 | "scale" : "2x" 11 | } 12 | ], 13 | "info" : { 14 | "version" : 1, 15 | "author" : "xcode" 16 | }, 17 | "properties" : { 18 | "template-rendering-intent" : "template" 19 | } 20 | } -------------------------------------------------------------------------------- /Kyapchar/Assets.xcassets/stop-2.imageset/stop-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vishaltelangre/Kyapchar/9520b0f072f26ebe2ba9a722786bab5d278aec41/Kyapchar/Assets.xcassets/stop-2.imageset/stop-2.png -------------------------------------------------------------------------------- /Kyapchar/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Kyapchar/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 0.0.5 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSApplicationCategoryType 26 | public.app-category.utilities 27 | LSMinimumSystemVersion 28 | $(MACOSX_DEPLOYMENT_TARGET) 29 | LSUIElement 30 | 31 | NSHumanReadableCopyright 32 | Copyright © 2016 Vishal Telangre. All rights reserved. 33 | NSMainNibFile 34 | MainMenu 35 | NSPrincipalClass 36 | NSApplication 37 | 38 | 39 | -------------------------------------------------------------------------------- /Kyapchar/LaunchAtLoginHelper.swift: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // LaunchAtLoginHelper.swift 4 | // 5 | // Created by Erica Sadun on 4/1/15. 6 | // Copyright (c) 2015 Erica Sadun. All rights reserved. 7 | // 8 | // Modified by Vishal Telangre on 06/25/2017. 9 | // Copyright (c) 2017 Vishal Telangre. All rights reserved. 10 | // 11 | 12 | import Foundation 13 | 14 | class LaunchAtLoginHelper { 15 | static func getLoginItems() -> LSSharedFileList? { 16 | let allocator : CFAllocator! = CFAllocatorGetDefault().takeUnretainedValue() 17 | let kLoginItems : CFString! = kLSSharedFileListSessionLoginItems.takeUnretainedValue() 18 | let loginItems_ = LSSharedFileListCreate(allocator, kLoginItems, nil) 19 | if loginItems_ == nil {return nil} 20 | let loginItems : LSSharedFileList! = loginItems_!.takeRetainedValue() 21 | return loginItems 22 | } 23 | 24 | static func existingItem(itemURL : NSURL) -> LSSharedFileListItem? { 25 | let loginItems_ = getLoginItems() 26 | if loginItems_ == nil {return nil} 27 | let loginItems = loginItems_! 28 | 29 | var seed : UInt32 = 0 30 | let currentItems = LSSharedFileListCopySnapshot(loginItems, &seed).takeRetainedValue() as? NSArray 31 | 32 | if currentItems == nil { 33 | return nil 34 | } 35 | 36 | for item in currentItems! { 37 | let resolutionFlags : UInt32 = UInt32(kLSSharedFileListNoUserInteraction | kLSSharedFileListDoNotMountVolumes) 38 | if item != nil { 39 | let unmanagedUrlRef = LSSharedFileListItemCopyResolvedURL(item as! LSSharedFileListItem, resolutionFlags, nil) 40 | if unmanagedUrlRef != nil { 41 | if let url = unmanagedUrlRef?.takeRetainedValue() as? NSURL { 42 | if url.isEqual(itemURL) { 43 | let result = item as! LSSharedFileListItem 44 | return result 45 | } 46 | } 47 | } 48 | 49 | } 50 | } 51 | 52 | return nil 53 | } 54 | 55 | static func willLaunchAtLogin(itemURL : NSURL) -> Bool { 56 | return existingItem(itemURL: itemURL) != nil 57 | } 58 | 59 | static func setLaunchAtLogin(itemURL: NSURL, enabled: Bool) -> Bool { 60 | let loginItems_ = getLoginItems() 61 | if loginItems_ == nil { 62 | return false 63 | } 64 | let loginItems = loginItems_! 65 | 66 | let item = existingItem(itemURL: itemURL) 67 | if item != nil && enabled { 68 | return true 69 | } 70 | if item != nil && !enabled { 71 | LSSharedFileListItemRemove(loginItems, item) 72 | return true 73 | } 74 | 75 | LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemBeforeFirst.takeUnretainedValue(), nil, nil, itemURL as CFURL, nil, nil) 76 | return true 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Kyapchar/MenuController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuController.swift 3 | // Kyapchar 4 | // 5 | // Created by Vishal Telangre on 11/1/16. 6 | // Copyright © 2016 Vishal Telangre. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class MenuController: NSObject { 12 | 13 | @IBOutlet weak var barMenu: NSMenu! 14 | @IBOutlet weak var recordStopItem: NSMenuItem! 15 | @IBOutlet weak var pauseResumeItem: NSMenuItem! 16 | 17 | let statusItem = NSStatusBar.system().statusItem(withLength: NSSquareStatusItemLength) 18 | 19 | var recorder: Recorder! 20 | 21 | var menubarIconAnimationTimer: Timer? 22 | var menubarIconCurrentIndex = 0 23 | var preferences: Preferences! 24 | 25 | @IBAction func onRecordStopItemClick(_ sender: NSMenuItem) { 26 | if recorder.recording { 27 | recorder.stop() 28 | recordStopItem.isEnabled = false 29 | recordStopItem.title = "Please wait..." 30 | } else { 31 | recorder.start() 32 | } 33 | } 34 | 35 | @IBAction func onPauseResumeItemClick(_ sender: NSMenuItem) { 36 | if recorder.paused { 37 | recorder.resume() 38 | } else { 39 | recorder.pause() 40 | } 41 | } 42 | 43 | @IBAction func onPreferencesItemClick(_ sender: NSMenuItem) { 44 | preferences = Preferences.init(windowNibName: "Preferences") 45 | preferences.showWindow(self) 46 | preferences.window?.makeKeyAndOrderFront(self) 47 | } 48 | 49 | @IBAction func onQuitItemClick(_ sender: NSMenuItem) { 50 | NSApplication.shared().terminate(self) 51 | } 52 | 53 | override func awakeFromNib() { 54 | statusItem.button?.image = NSImage(named: "record") 55 | 56 | pauseResumeItem.isEnabled = false 57 | 58 | statusItem.menu = barMenu 59 | recorder = Recorder(delegate: self) 60 | } 61 | 62 | func animateMenubarIcon() { 63 | menubarIconAnimationTimer = Timer.scheduledTimer(timeInterval: 1/2, target: self, selector: #selector(MenuController.changeMenubarIcon), userInfo: nil, repeats: true) 64 | } 65 | 66 | func stopMenubarIconAnimation() { 67 | menubarIconAnimationTimer?.invalidate() 68 | } 69 | 70 | func changeMenubarIcon() { 71 | statusItem.button?.image = NSImage(named: NSString(format: "stop-%d", (menubarIconCurrentIndex + 1)) as String) 72 | 73 | menubarIconCurrentIndex += 1 74 | 75 | if menubarIconCurrentIndex > 1 { 76 | menubarIconCurrentIndex = 0 77 | } 78 | } 79 | 80 | } 81 | 82 | extension MenuController: RecorderDelegate { 83 | 84 | func recordingDidStart(_ recordingInfo: RecordingInfo?) { 85 | recordStopItem.title = "Stop" 86 | animateMenubarIcon() 87 | 88 | pauseResumeItem.isEnabled = true 89 | pauseResumeItem.title = "Pause" 90 | } 91 | 92 | func recordingDidStop(_ recordingInfo: RecordingInfo?) { 93 | recordStopItem.isEnabled = true 94 | recordStopItem.title = "Record" 95 | 96 | stopMenubarIconAnimation() 97 | 98 | statusItem.button?.image = NSImage(named: "record") 99 | 100 | pauseResumeItem.isEnabled = false 101 | pauseResumeItem.title = "Pause" 102 | 103 | recorder = nil 104 | recorder = Recorder(delegate: self) 105 | 106 | if recordingInfo?.location != nil { 107 | NSWorkspace.shared().activateFileViewerSelecting([recordingInfo!.location as URL]) 108 | } 109 | } 110 | 111 | func recordingDidResume(_ recordingInfo: RecordingInfo?) { 112 | pauseResumeItem.title = "Pause" 113 | animateMenubarIcon() 114 | } 115 | 116 | func recordingDidPause(_ recordingInfo: RecordingInfo?) { 117 | pauseResumeItem.title = "Resume" 118 | 119 | stopMenubarIconAnimation() 120 | 121 | statusItem.button?.image = NSImage(named: "pause") 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /Kyapchar/Preferences.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Preferences.swift 3 | // Kyapchar 4 | // 5 | // Created by Vishal Telangre on 6/23/17. 6 | // Copyright © 2017 Vishal Telangre. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import ServiceManagement 11 | 12 | class Preferences: NSWindowController { 13 | @IBOutlet weak var saveLocationLabel: NSTextField! 14 | @IBOutlet weak var versionLabel: NSTextField! 15 | @IBOutlet weak var launchAtLoginCheckbox: NSButton! 16 | 17 | let appURL = NSURL(fileURLWithPath: Bundle.main.bundlePath) 18 | 19 | @IBAction func onSaveLocationChangeClick(_ sender: NSButton) { 20 | let dialog = NSOpenPanel(); 21 | dialog.title = "Choose location where recorded videos should be saved" 22 | dialog.directoryURL = UserDefaults.standard.url(forKey: "KyapcharSaveLocation") 23 | dialog.showsResizeIndicator = true; 24 | dialog.showsHiddenFiles = false; 25 | dialog.canChooseDirectories = true; 26 | dialog.canChooseFiles = false; 27 | dialog.canCreateDirectories = true; 28 | dialog.allowsMultipleSelection = false; 29 | 30 | if (dialog.runModal() == NSModalResponseOK) { 31 | let result = dialog.url 32 | 33 | if (result != nil) { 34 | UserDefaults.standard.set(dialog.url, forKey: "KyapcharSaveLocation") 35 | saveLocationLabel.stringValue = result!.path 36 | saveLocationLabel.toolTip = result!.path 37 | } 38 | } 39 | } 40 | 41 | @IBAction func onLaunchAtLoginClick(_ sender: NSButton) { 42 | let autoLaunch = launchAtLoginCheckbox.state == NSOnState 43 | if LaunchAtLoginHelper.setLaunchAtLogin(itemURL: appURL, enabled: autoLaunch) { 44 | if autoLaunch { 45 | NSLog("Successfully added as a login item") 46 | } else { 47 | NSLog("Successfully removed as a login item") 48 | } 49 | } else { 50 | NSLog("Failed to add as a login item") 51 | } 52 | } 53 | 54 | @IBAction func onGithubLinkClick(_ sender: NSButton) { 55 | NSWorkspace.shared().open(URL(string: sender.title)!) 56 | } 57 | 58 | override func windowDidLoad() { 59 | super.windowDidLoad() 60 | 61 | let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String 62 | versionLabel.stringValue = "Build v\(version)" 63 | 64 | let configuredSaveLocation = UserDefaults.standard.url(forKey: "KyapcharSaveLocation") 65 | saveLocationLabel.stringValue = configuredSaveLocation!.path 66 | saveLocationLabel.toolTip = configuredSaveLocation!.path 67 | 68 | launchAtLoginCheckbox.state = LaunchAtLoginHelper.willLaunchAtLogin(itemURL: appURL) ? NSOnState : NSOffState 69 | NSApp.activate(ignoringOtherApps: true) 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /Kyapchar/Preferences.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 66 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /Kyapchar/Recorder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Recorder.swift 3 | // Kyapchar 4 | // 5 | // Created by Vishal Telangre on 11/1/16. 6 | // Copyright © 2016 Vishal Telangre. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AVFoundation 11 | import Cocoa 12 | 13 | struct RecordingInfo { 14 | var location: URL 15 | var duration: Int 16 | var size: Float 17 | } 18 | 19 | protocol RecorderDelegate { 20 | func recordingDidStart(_ recordingInfo: RecordingInfo?) 21 | func recordingDidStop(_ recordingInfo: RecordingInfo?) 22 | func recordingDidResume(_ recordingInfo: RecordingInfo?) 23 | func recordingDidPause(_ recordingInfo: RecordingInfo?) 24 | } 25 | 26 | class Recorder: NSObject { 27 | 28 | var delegate: RecorderDelegate? 29 | 30 | var session: AVCaptureSession! 31 | var output: AVCaptureMovieFileOutput! 32 | var audioRecorder: AVAudioRecorder! 33 | var castedVideoURL = URL(fileURLWithPath: "") 34 | var micAudioURL = URL(fileURLWithPath: "") 35 | var finalVideoURL = URL(fileURLWithPath: "") 36 | var recordingInfo: RecordingInfo! 37 | var recording = false 38 | var paused = false 39 | 40 | let micAudioRecordSettings = [AVSampleRateKey : NSNumber(value: Float(44100.0) as Float), 41 | AVFormatIDKey : NSNumber(value: Int32(kAudioFormatMPEG4AAC) as Int32), 42 | AVNumberOfChannelsKey : NSNumber(value: 1 as Int32), 43 | AVEncoderAudioQualityKey : NSNumber(value: Int32(AVAudioQuality.medium.rawValue) as Int32)] 44 | 45 | init(delegate: RecorderDelegate) { 46 | self.delegate = delegate 47 | } 48 | 49 | func start() { 50 | session = AVCaptureSession() 51 | output = AVCaptureMovieFileOutput() 52 | (castedVideoURL, micAudioURL, finalVideoURL) = filePaths() 53 | 54 | let input = AVCaptureScreenInput(displayID: CGMainDisplayID()) 55 | let screen = NSScreen.main()! 56 | let screenRect = screen.frame 57 | 58 | do { 59 | audioRecorder = try AVAudioRecorder(url: micAudioURL, settings: micAudioRecordSettings) 60 | } catch { 61 | print("Cannot initialize AVAudioRecorder: \(error)") 62 | return 63 | } 64 | 65 | audioRecorder?.isMeteringEnabled = true 66 | audioRecorder?.prepareToRecord() 67 | 68 | input?.cropRect = screenRect 69 | input?.minFrameDuration = CMTimeMake(1, 1000) 70 | 71 | if !session!.canAddInput(input) { 72 | return 73 | } 74 | session!.addInput(input) 75 | 76 | if !session!.canAddOutput(output) { 77 | return 78 | } 79 | session!.addOutput(output) 80 | 81 | session?.startRunning() 82 | 83 | output!.startRecording(toOutputFileURL: castedVideoURL, recordingDelegate: self) 84 | 85 | recording = true 86 | delegate?.recordingDidStart(nil) 87 | } 88 | 89 | func stop() { 90 | if output != nil { 91 | recording = false 92 | output?.stopRecording() 93 | } 94 | } 95 | 96 | func pause() { 97 | if output.isRecording { 98 | output.pauseRecording() 99 | paused = true 100 | delegate?.recordingDidPause(nil) 101 | } 102 | } 103 | 104 | func resume() { 105 | if output.isRecordingPaused { 106 | output.resumeRecording() 107 | paused = false 108 | delegate?.recordingDidResume(nil) 109 | } 110 | } 111 | 112 | func generateFinalVideo() { 113 | let mixComposition = AVMutableComposition() 114 | let micAudioAsset = AVAsset(url: micAudioURL) 115 | let castedVideoAsset = AVAsset(url: castedVideoURL) 116 | let micAudioTimeRange = CMTimeRangeMake(kCMTimeZero, micAudioAsset.duration) 117 | let castedVideoTimeRange = CMTimeRangeMake(kCMTimeZero, castedVideoAsset.duration) 118 | let compositionAudioTrack = mixComposition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: kCMPersistentTrackID_Invalid) 119 | let compositionVideoTrack = mixComposition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: kCMPersistentTrackID_Invalid) 120 | 121 | do { 122 | let track: AVAssetTrack = micAudioAsset.tracks(withMediaType: AVMediaTypeAudio).first! 123 | try compositionAudioTrack.insertTimeRange(micAudioTimeRange, of: track, at: kCMTimeZero) 124 | } catch { 125 | print("Error while adding micAudioAsset to compositionAudioTrack: \(error)") 126 | self.delegate?.recordingDidStop(nil) 127 | } 128 | 129 | do { 130 | let track: AVAssetTrack = castedVideoAsset.tracks(withMediaType: AVMediaTypeVideo).first! 131 | try compositionVideoTrack.insertTimeRange(castedVideoTimeRange, of: track, at: kCMTimeZero) 132 | } catch { 133 | print("Error while adding castedVideoAsset to compositionVideoTrack: \(error)") 134 | self.delegate?.recordingDidStop(nil) 135 | } 136 | 137 | let assetExportSession = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) 138 | assetExportSession?.outputFileType = "com.apple.quicktime-movie" 139 | assetExportSession?.outputURL = finalVideoURL 140 | assetExportSession?.exportAsynchronously(completionHandler: { 141 | DispatchQueue.global(qos: DispatchQoS.QoSClass.background).async(execute: { 142 | if assetExportSession?.status == AVAssetExportSessionStatus.completed { 143 | var size: Float = 0.0 144 | 145 | do { 146 | let attr : NSDictionary? = try FileManager.default.attributesOfItem(atPath: self.finalVideoURL.path) as NSDictionary? 147 | if let _attr = attr { 148 | size = Float(_attr.fileSize()/(1024*1024)); 149 | } 150 | } catch { 151 | print("Error while fetching final video file size: \(error)") 152 | } 153 | 154 | let duration = Int(CMTimeGetSeconds((assetExportSession?.asset.duration)!)) 155 | let location = self.finalVideoURL 156 | self.recordingInfo = RecordingInfo(location: location, duration: duration, size: size) 157 | 158 | } else { 159 | print("Export failed") 160 | self.recordingInfo = nil 161 | } 162 | 163 | do { 164 | try FileManager.default.removeItem(at: self.castedVideoURL) 165 | try FileManager.default.removeItem(at: self.micAudioURL) 166 | } catch { 167 | print("Error while deleting temporary files: \(error)") 168 | } 169 | 170 | self.delegate?.recordingDidStop(self.recordingInfo) 171 | }) 172 | }) 173 | } 174 | 175 | func filePaths() -> (URL, URL, URL) { 176 | let date = Date() 177 | let calendar = Calendar.current 178 | let components = (calendar as NSCalendar).components([.hour, .minute, .second, .day, .month, .year], from: date) 179 | let destinationURL: URL = UserDefaults.standard.url(forKey: "KyapcharSaveLocation")! 180 | let finalVideoFilename = "Kyapchar_\(components.day!)-\(components.month!)-\(components.year!)_\(components.hour!):\(components.minute!):\(components.second!).mov" 181 | let finalVideoPath = destinationURL.appendingPathComponent(finalVideoFilename) 182 | let filename = date.timeIntervalSince1970 * 1000 183 | let tempVideoFilePath = URL(fileURLWithPath: "/tmp/\(filename).mp4") 184 | let tempMicAudioFilePath = URL(fileURLWithPath: "/tmp/\(filename).m4a") 185 | 186 | return (tempVideoFilePath, tempMicAudioFilePath, finalVideoPath) 187 | } 188 | } 189 | 190 | extension Recorder: AVCaptureFileOutputRecordingDelegate { 191 | func capture(_ captureOutput: AVCaptureFileOutput!, didPauseRecordingToOutputFileAt fileURL: URL!, fromConnections connections: [Any]!) { 192 | audioRecorder.pause() 193 | } 194 | 195 | func capture(_ captureOutput: AVCaptureFileOutput!, didResumeRecordingToOutputFileAt fileURL: URL!, fromConnections connections: [Any]!) { 196 | audioRecorder.record() 197 | } 198 | 199 | func capture(_ captureOutput: AVCaptureFileOutput!, didStartRecordingToOutputFileAt fileURL: URL!, fromConnections connections: [Any]!) { 200 | audioRecorder.record() 201 | } 202 | 203 | func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, fromConnections connections: [Any]!, error: Error!) { 204 | if (error != nil) { 205 | debugPrint(error) 206 | return 207 | } 208 | 209 | DispatchQueue.global(qos: DispatchQoS.QoSClass.background).async { 210 | self.session?.stopRunning() 211 | self.audioRecorder?.stop() 212 | 213 | self.generateFinalVideo() 214 | 215 | self.session = nil 216 | self.output = nil 217 | self.audioRecorder = nil 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kyapchar 2 | 3 | ![kyapchar](https://cloud.githubusercontent.com/assets/876195/19885931/51c02120-a047-11e6-9dc2-6a6e68e8520b.png) 4 | 5 | Simple screen and microphone audio recorder for Mac 6 | 7 | ## Sneak Peek 8 | 9 | Following GIF image is a preview of this simple recorder which stays in toolbar! 10 | 11 | ![preview.gif](https://user-images.githubusercontent.com/876195/42590994-f1416784-8562-11e8-820a-e1cd840a5a35.gif) 12 | 13 | ## Download 14 | 15 | Download the latest zip file from from the [releases](https://github.com/vishaltelangre/Kyapchar/releases) page. 16 | 17 | Extract it and move the compressed `Kypachar.app` from it into `Applications` folder displayed in the Finder app's sidebar. 18 | 19 | ## Development 20 | 21 | If you want to contribute or hack the source, then you're more than welcome! 22 | 23 | Open `Kyapchar.xcodeproj` with XCode app, and hit `cmd (⌘)` and `R` keys together to build and run it locally. 24 | 25 | ## Copyright and License 26 | 27 | Copyright (c) 2016-18, Vishal Telangre. All Rights Reserved. 28 | 29 | This project is licenced under the [MIT License](LICENSE). 30 | 31 | --------------------------------------------------------------------------------