Skip to content

Commit

Permalink
Add support to record mic audio as well
Browse files Browse the repository at this point in the history
  • Loading branch information
vishaltelangre committed Oct 3, 2016
1 parent 3efb4da commit ed409b3
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 39 deletions.
1 change: 1 addition & 0 deletions Kyapchar.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@
08DE4F941DA00B9000D969DA /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
Expand Down
2 changes: 1 addition & 1 deletion Kyapchar/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>0.0.1</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
Expand Down
133 changes: 99 additions & 34 deletions Kyapchar/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,17 @@ import AVFoundation
import AppKit

class ViewController: NSViewController, AVCaptureFileOutputRecordingDelegate {
var session: AVCaptureSession? = AVCaptureSession()
var output: AVCaptureMovieFileOutput? = AVCaptureMovieFileOutput()
var session: AVCaptureSession!
var output: AVCaptureMovieFileOutput!
var audioRecorder: AVAudioRecorder!
var castedVideoURL = NSURL()
var micAudioURL = NSURL()
var finalVideoURL = NSURL()

let micAudioRecordSettings = [AVSampleRateKey : NSNumber(float: Float(44100.0)),
AVFormatIDKey : NSNumber(int: Int32(kAudioFormatMPEG4AAC)),
AVNumberOfChannelsKey : NSNumber(int: 1),
AVEncoderAudioQualityKey : NSNumber(int: Int32(AVAudioQuality.Medium.rawValue))]

@IBOutlet weak var recordButton: NSButton!
@IBOutlet weak var durationLabel: NSTextField!
Expand All @@ -35,18 +44,22 @@ class ViewController: NSViewController, AVCaptureFileOutputRecordingDelegate {
}

func startRecording() {
if session == nil {
session = AVCaptureSession()
}

if output == nil {
output = AVCaptureMovieFileOutput()
}
session = AVCaptureSession()
output = AVCaptureMovieFileOutput()
(castedVideoURL, micAudioURL, finalVideoURL) = filePaths()

let input = AVCaptureScreenInput(displayID: CGMainDisplayID())
let screen = NSScreen.mainScreen()!
let screenRect = screen.frame
let destination: NSURL = unqiueDestination()

do {
audioRecorder = try AVAudioRecorder(URL: micAudioURL, settings: micAudioRecordSettings)
} catch {
print("Cannot initialize AVAudioRecorder: \(error)")
}

audioRecorder?.meteringEnabled = true
audioRecorder?.prepareToRecord()

input.cropRect = screenRect
input.minFrameDuration = CMTimeMake(1, 1000)
Expand All @@ -63,32 +76,15 @@ class ViewController: NSViewController, AVCaptureFileOutputRecordingDelegate {

session?.startRunning()

do {
try NSFileManager.defaultManager().removeItemAtURL(destination)
} catch {
print("Error while deleting file: \(error)")
}

output!.startRecordingToOutputFileURL(destination, recordingDelegate: self)
output!.startRecordingToOutputFileURL(castedVideoURL, recordingDelegate: self)
audioRecorder.record()
}

func stopRecording() {
if output != nil {
output!.stopRecording()
let duration: Int = Int(CMTimeGetSeconds(output!.recordedDuration))
let location = output!.outputFileURL.absoluteString
var fileSize : Float = 0.0

do {
let attr : NSDictionary? = try NSFileManager.defaultManager().attributesOfItemAtPath(output!.outputFileURL.path!)
if let _attr = attr {
fileSize = Float(_attr.fileSize()/(1024*1024));
}
} catch {
print("Error while fetching recorded file size: \(error)")
}

durationLabel.stringValue = "\(formatDuration(duration)) Seconds\n📁 \(location)\nSize \(String(format: "%.2f", fileSize)) MB"
audioRecorder?.stop()
durationLabel.stringValue = "Please wait..."
}
}

Expand All @@ -99,19 +95,88 @@ class ViewController: NSViewController, AVCaptureFileOutputRecordingDelegate {

dispatch_async(dispatch_get_main_queue()) {
self.session?.stopRunning()

self.generateFinalVideo()

self.session = nil
self.output = nil
self.audioRecorder = nil
}
}

func generateFinalVideo() {
let mixComposition = AVMutableComposition()
let micAudioAsset = AVAsset(URL: micAudioURL)
let castedVideoAsset = AVAsset(URL: castedVideoURL)
let micAudioTimeRange = CMTimeRangeMake(kCMTimeZero, micAudioAsset.duration)
let castedVideoTimeRange = CMTimeRangeMake(kCMTimeZero, castedVideoAsset.duration)
let compositionAudioTrack = mixComposition.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: kCMPersistentTrackID_Invalid)
let compositionVideoTrack = mixComposition.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: kCMPersistentTrackID_Invalid)

do {
let track: AVAssetTrack = micAudioAsset.tracksWithMediaType(AVMediaTypeAudio).first!
try compositionAudioTrack.insertTimeRange(micAudioTimeRange, ofTrack: track, atTime: kCMTimeZero)
} catch {
print("Error while adding micAudioAsset to compositionAudioTrack: \(error)")
}

do {
let track: AVAssetTrack = castedVideoAsset.tracksWithMediaType(AVMediaTypeVideo).first!
try compositionVideoTrack.insertTimeRange(castedVideoTimeRange, ofTrack: track, atTime: kCMTimeZero)
} catch {
print("Error while adding castedVideoAsset to compositionVideoTrack: \(error)")
}

let assetExportSession = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality)
assetExportSession?.outputFileType = "com.apple.quicktime-movie"
assetExportSession?.outputURL = finalVideoURL
assetExportSession?.exportAsynchronouslyWithCompletionHandler({
dispatch_async(dispatch_get_main_queue(), {
if assetExportSession!.status == AVAssetExportSessionStatus.Completed {
let duration: Int = Int(CMTimeGetSeconds(castedVideoAsset.duration))
var fileSize : Float = 0.0

do {
let attr : NSDictionary? = try NSFileManager.defaultManager().attributesOfItemAtPath(self.finalVideoURL.path!)
if let _attr = attr {
fileSize = Float(_attr.fileSize()/(1024*1024));
}
} catch {
print("Error while fetching final video file size: \(error)")
}


self.durationLabel.stringValue = "\(self.formatDuration(duration)) Seconds\n📁 \(self.finalVideoURL.absoluteString)\nSize \(String(format: "%.2f", fileSize)) MB"
} else {
print("Export failed")
self.durationLabel.stringValue = "Export failed!"
}

do {
try NSFileManager.defaultManager().removeItemAtURL(self.castedVideoURL)
try NSFileManager.defaultManager().removeItemAtURL(self.micAudioURL)
} catch {
print("Error while deleting temporary files: \(error)")
}

})
})
}

func unqiueDestination() -> NSURL {
func filePaths() -> (NSURL, NSURL, NSURL) {
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components([.Hour, .Minute, .Second, .Day, .Month, .Year], fromDate: date)
let moviesDirectoryURL: NSURL = NSFileManager.defaultManager().URLsForDirectory(.MoviesDirectory, inDomains: .UserDomainMask)[0]
let fileName = "Kyapchar_\(components.day)-\(components.month)-\(components.year)_\(components.hour):\(components.minute):\(components.second).mov"
let finalVideoFilename = "Kyapchar_\(components.day)-\(components.month)-\(components.year)_\(components.hour):\(components.minute):\(components.second).mov"
let finalVideoPath = moviesDirectoryURL.URLByAppendingPathComponent(finalVideoFilename)
let filename = date.timeIntervalSince1970 * 1000
let tempVideoFilePath = NSURL(fileURLWithPath: "/tmp/\(filename).mp4")
let tempMicAudioFilePath = NSURL(fileURLWithPath: "/tmp/\(filename).m4a")

print("Paths: tempVideoFilePath: \(tempVideoFilePath), tempMicAudioFilePath: \(tempMicAudioFilePath), finalVideoPath: \(finalVideoPath)")

return moviesDirectoryURL.URLByAppendingPathComponent(fileName)
return (tempVideoFilePath, tempMicAudioFilePath, finalVideoPath)
}

func formatDuration(seconds: Int) -> String {
Expand Down
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
# Kyapchar

Simple screen recorder for Mac
Simple screen and microphone audio recorder for Mac

## Preview

Watch this demo screencast captured using Kyapchar - https://youtu.be/B4RfdJCZ6yU.

![Preview](https://monosnap.com/file/zyTid534mXjmJbY7G8GYNPUzMejT64.png)

## In a early development stage!
## Notice

This software is in its very early development stage. It is not very well tested. Therefore it may cause unexpected problems.
So be cautious while using it.

## Development
## How to run?

Open `Kyapchar.xcodeproj` with XCode.
Open `Kyapchar.xcodeproj` with XCode, and hit `Command (⌘)-R` keys to build and run it locally.

## Copyright and License

Expand Down

0 comments on commit ed409b3

Please sign in to comment.