Skip to content

Commit

Permalink
Finish app and icon
Browse files Browse the repository at this point in the history
  • Loading branch information
sindresorhus committed Feb 22, 2018
1 parent 24cbf06 commit b3e328c
Show file tree
Hide file tree
Showing 22 changed files with 468 additions and 110 deletions.
8 changes: 8 additions & 0 deletions Gifski.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
E3AE628C1E5CD2F300035A2F /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = E3AE628A1E5CD2F300035A2F /* MainMenu.xib */; };
E3CB1DD71F7E4CBC00D79BFC /* VideoDropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3CB1DD61F7E4CBC00D79BFC /* VideoDropView.swift */; };
E3D08F6E1E5D7BFD00F465DF /* util.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3D08F6D1E5D7BFD00F465DF /* util.swift */; };
E3DF3E88203BD2B900055855 /* SavePanelAccessoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3DF3E86203BD2B900055855 /* SavePanelAccessoryViewController.swift */; };
E3DF3E89203BD2B900055855 /* SavePanelAccessoryViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = E3DF3E87203BD2B900055855 /* SavePanelAccessoryViewController.xib */; };
E3FD61AE201BD3A80087160A /* libgifski.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = E3FD61A1201BD29E0087160A /* libgifski.dylib */; };
/* End PBXBuildFile section */

Expand Down Expand Up @@ -80,6 +82,8 @@
E3BF14CC1E5CD5A30049FD4B /* Gifski.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Gifski.entitlements; sourceTree = "<group>"; };
E3CB1DD61F7E4CBC00D79BFC /* VideoDropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDropView.swift; sourceTree = "<group>"; };
E3D08F6D1E5D7BFD00F465DF /* util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = util.swift; sourceTree = "<group>"; };
E3DF3E86203BD2B900055855 /* SavePanelAccessoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SavePanelAccessoryViewController.swift; sourceTree = "<group>"; };
E3DF3E87203BD2B900055855 /* SavePanelAccessoryViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SavePanelAccessoryViewController.xib; sourceTree = "<group>"; };
E3FD6190201BCBC30087160A /* Gifski-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Gifski-Bridging-Header.h"; sourceTree = "<group>"; };
E3FD6197201BD29E0087160A /* gifski.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = gifski.xcodeproj; path = "gifski-api/gifski.xcodeproj"; sourceTree = "<group>"; };
E3FD61A4201BD2DA0087160A /* gifski.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = gifski.h; path = "gifski-api/gifski.h"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -134,6 +138,8 @@
E3AE62861E5CD2F300035A2F /* AppDelegate.swift */,
E3CB1DD61F7E4CBC00D79BFC /* VideoDropView.swift */,
E339F010203820ED003B78FB /* Gifski.swift */,
E3DF3E86203BD2B900055855 /* SavePanelAccessoryViewController.swift */,
E3DF3E87203BD2B900055855 /* SavePanelAccessoryViewController.xib */,
E3D08F6D1E5D7BFD00F465DF /* util.swift */,
E3AE62881E5CD2F300035A2F /* Assets.xcassets */,
E3AE628A1E5CD2F300035A2F /* MainMenu.xib */,
Expand Down Expand Up @@ -255,6 +261,7 @@
files = (
E3AE62891E5CD2F300035A2F /* Assets.xcassets in Resources */,
E3AE628C1E5CD2F300035A2F /* MainMenu.xib in Resources */,
E3DF3E89203BD2B900055855 /* SavePanelAccessoryViewController.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -286,6 +293,7 @@
E3CB1DD71F7E4CBC00D79BFC /* VideoDropView.swift in Sources */,
E3AE62871E5CD2F300035A2F /* AppDelegate.swift in Sources */,
E3D08F6E1E5D7BFD00F465DF /* util.swift in Sources */,
E3DF3E88203BD2B900055855 /* SavePanelAccessoryViewController.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
109 changes: 38 additions & 71 deletions Gifski/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,66 +10,35 @@ extension NSColor {
@NSApplicationMain
final class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet private weak var window: NSWindow!
var imageView: NSImageView!
var imageDropView: VideoDropView!
var gifski = Gifski()

lazy var outputQualitySlider: NSSlider = {
let slider = NSSlider()
slider.frame = CGRect(width: 150, height: 32)
slider.numberOfTickMarks = 10
slider.allowsTickMarkValuesOnly = true
slider.doubleValue = defaults["outputQuality"] as! Double

slider.onAction = { _ in
defaults["outputQuality"] = slider.doubleValue
}

return slider
}()

lazy var outputQualityView: NSView = {
let view = NSView()
view.addSubview(outputQualitySlider)
view.frame.width = 280
view.frame.height = 32

let qualityLabel = Label(text: "Quality:")
qualityLabel.frame.y = 9
view.addSubview(qualityLabel)
outputQualitySlider.frame.x = qualityLabel.frame.right

return view
}()
var videoDropView: VideoDropView!
let gifski = Gifski()

lazy var circularProgress: CircularProgressView = {
let size: CGFloat = 160
let view = CircularProgressView(frame: CGRect(widthHeight: size))
view.centerInWindow(window)
view.foreground = .appTheme
view.strokeWidth = 2
view.percentLabelLayer.contentsScale = 2 /// TODO: Find out why I must set this
view.percentLabelLayer.disableAnimation()
view.percentLabelLayer.setAutomaticContentsScale()
view.percentLabelLayer.implicitAnimations = false
view.layer?.backgroundColor = .clear
view.isHidden = true
return view
}()

lazy var dropToConvertLabel: NSTextField = {
let text = NSAttributedString(string: "Drop a Video to Convert")
.applying(attributes: [.font: NSFont.systemFont(ofSize: 15, weight: .regular)])
.colored(with: .appTheme)
let label = NSTextField(labelWithAttributedString: text)
label.frame = window.contentView!.frame.centered(size: label.frame.size)
return label
}()

var isInInitialState = true
var hasFinishedLaunching = false
var urlsToConvertOnLaunch: URL!
var choosenDimensions: CGSize!

@objc dynamic var isRunning: Bool = false {
didSet {
imageDropView.isHidden = isRunning
if isRunning {
videoDropView.isHidden = true
} else {
videoDropView.fadeIn()
}

circularProgress.isHidden = !isRunning
}
}

Expand Down Expand Up @@ -101,14 +70,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

let view = window.contentView!

/// TODO: Move this to a NSViewController
view.addSubview(dropToConvertLabel)
view.addSubview(circularProgress)

imageDropView = VideoDropView(frame: view.frame)
imageDropView.onComplete = { url in
videoDropView = VideoDropView(frame: view.frame)
videoDropView.dropText = "Drop a Video to Convert to GIF"
videoDropView.onComplete = { url in
self.convert(url.first!)
}
view.addSubview(imageDropView, positioned: .above, relativeTo: nil)
view.addSubview(videoDropView, positioned: .above, relativeTo: nil)

window.makeKeyAndOrderFront(nil) /// TODO: This is dirty, find a better way

Expand All @@ -122,17 +91,20 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
return
}

guard let imageUrls = (urls.first { $0.typeIdentifier == "public.movie" }) else {
guard urls.count == 1 else {
Misc.alert(title: "Max one file", text: "You can only convert a single file at the time")
return
}

let videoUrl = urls.first!

/// TODO: Simplify this. Make a function that calls the input when the app finished launching, or right away if it already has.
if hasFinishedLaunching {
convert(imageUrls)
convert(videoUrl)
} else {
// This method is called before `applicationDidFinishLaunching`,
// so we buffer it up if images are "Open with" this app
urlsToConvertOnLaunch = imageUrls
// so we buffer it up a video is "Open with" this app
urlsToConvertOnLaunch = videoUrl
}
}

Expand All @@ -151,7 +123,6 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
let panel = NSOpenPanel()
panel.canChooseDirectories = false
panel.canCreateDirectories = false
panel.allowsMultipleSelection = true
panel.allowedFileTypes = ["public.movie"]

panel.beginSheetModal(for: window) {
Expand All @@ -166,26 +137,23 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

if progress == 1 {
circularProgress.percentLabelLayer.string = ""
isRunning = false
circularProgress.fadeOut(delay: 1) {
self.isRunning = false
}
}
}

func createSaveAccessoryView() -> NSView {
let view = NSView(frame: CGRect(width: 280, height: 32))
view.autoresizingMask = [.minXMargin, .maxXMargin]
// TODO: Use auto-layout here to place and size the controls
view.addSubview(outputQualityView)
return view
}

func convert(_ inputUrl: URL) {
let panel = NSSavePanel()
panel.canCreateDirectories = true
panel.directoryURL = inputUrl.directoryURL
panel.nameFieldStringValue = inputUrl.changingFileExtension(to: "gif").filename
panel.prompt = "Convert"
panel.message = "Choose where to save the GIF"
panel.accessoryView = createSaveAccessoryView()

let accessoryViewController = SavePanelAccessoryViewController()
accessoryViewController.inputUrl = inputUrl
panel.accessoryView = accessoryViewController.view

panel.beginSheetModal(for: window) {
if $0 == .OK {
Expand All @@ -201,16 +169,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate {

isRunning = true

if isInInitialState {
isInInitialState = false
window.contentView!.fadeOut(dropToConvertLabel)
window.contentView!.fadeIn(circularProgress)
}

circularProgress.animated = false
circularProgress.progress = 0
circularProgress.animated = true

gifski.convertFile(inputUrl, outputFile: outputUrl, quality: defaults["outputQuality"] as! Double)
gifski.convertFile(
inputUrl,
outputFile: outputUrl,
quality: defaults["outputQuality"] as! Double,
dimensions: choosenDimensions
)
}
}
Binary file modified Gifski/Assets.xcassets/AppIcon.appiconset/icon_128x128.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Gifski/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Gifski/Assets.xcassets/AppIcon.appiconset/icon_16x16.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Gifski/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Gifski/Assets.xcassets/AppIcon.appiconset/icon_256x256.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Gifski/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Gifski/Assets.xcassets/AppIcon.appiconset/icon_32x32.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Gifski/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Gifski/Assets.xcassets/AppIcon.appiconset/icon_512x512.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Gifski/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 15 additions & 4 deletions Gifski/Gifski.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,27 @@ final class Gifski {
private(set) var progress: Double = 0
var onProgress: ((_ progress: Double) -> Void)?

func convertFile(_ inputFile: URL, outputFile: URL, quality: Double = 1) {
func convertFile(
_ inputFile: URL,
outputFile: URL,
quality: Double = 1,
dimensions: CGSize? = nil
) {
guard !isRunning else {
return
}

isRunning = true
frameCount = 0
frameIndex = 0
isRunning = true

var settings = GifskiSettings(width: 0, height: 0, quality: UInt8(quality * 100), once: false, fast: false)
var settings = GifskiSettings(
width: UInt32(dimensions?.width ?? 0),
height: UInt32(dimensions?.height ?? 0),
quality: UInt8(quality * 100),
once: false,
fast: false
)
let g = gifski_new(&settings)

let context = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
Expand Down Expand Up @@ -71,7 +82,7 @@ final class Gifski {
g,
UInt32(frameIndex),
UInt32(image.width),
UInt32(image.bytesPerRow),
UInt32(image.bytesPerRow),
UInt32(image.height),
buffer,
UInt16(100 / FPS)
Expand Down
4 changes: 2 additions & 2 deletions Gifski/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
<array>
<dict>
<key>CFBundleTypeName</key>
<string>HEIC Image</string>
<string>Video</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSItemContentTypes</key>
<array>
<string>public.heic</string>
<string>public.movie</string>
</array>
</dict>
</array>
Expand Down
47 changes: 47 additions & 0 deletions Gifski/SavePanelAccessoryViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import Cocoa

final class SavePanelAccessoryViewController: NSViewController {
@IBOutlet private weak var dimensionsSlider: NSSlider!
@IBOutlet private weak var dimensionsLabel: NSTextField!
@IBOutlet private weak var qualitySlider: NSSlider!
@IBOutlet private weak var estimatedSizeLabel: NSTextField!
var inputUrl: URL!

override func viewDidLoad() {
super.viewDidLoad()

view.autoresizingMask = [.minXMargin, .maxXMargin]

let formatter = ByteCountFormatter()
formatter.zeroPadsFractionDigits = true

/// TODO: Use KVO here

let metadata = inputUrl.videoMetadata!
let FPS = 24.0
let frameCount = metadata.duration * FPS
var currentDimensions = metadata.dimensions

func estimateFileSize() {
var fileSize = (Double(currentDimensions.width) * Double(currentDimensions.height) * Double(frameCount)) / 3
fileSize = fileSize * (qualitySlider.doubleValue + 1.5) / 2.5
estimatedSizeLabel.stringValue = formatter.string(fromByteCount: Int64(fileSize))
}

dimensionsSlider.onAction = { _ in
currentDimensions = metadata.dimensions * self.dimensionsSlider.doubleValue
self.dimensionsLabel.stringValue = "\(Int(currentDimensions.width))×\(Int(currentDimensions.height))"
estimateFileSize()

/// TODO: Feels hacky to do it this way. Find a better way to pass the state.
self.appDelegate.choosenDimensions = currentDimensions
}

qualitySlider.onAction = { _ in
UserDefaults.standard["outputQuality"] = self.qualitySlider.doubleValue
estimateFileSize()
}

dimensionsSlider.triggerAction()
}
}

0 comments on commit b3e328c

Please sign in to comment.