├── .github ├── FUNDING.yml └── workflows │ └── ci-mac.yaml ├── .gitignore ├── .gitmodules ├── Assets ├── Icon.psd └── Screenshot.png ├── CODE_OF_CONDUCT.md ├── LICENSE ├── PhotoConverter.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── Photo Converter.xcscheme ├── PhotoConverter ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── 128.png │ │ ├── 128@2x.png │ │ ├── 16.png │ │ ├── 16@2x.png │ │ ├── 256.png │ │ ├── 256@2x.png │ │ ├── 32.png │ │ ├── 32@2x.png │ │ ├── 512.png │ │ ├── 512@2x.png │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── MainMenu.xib ├── Classes │ ├── AboutWindowController.swift │ ├── ApplicationDelegate.swift │ ├── Base.lproj │ │ ├── AboutWindowController.xib │ │ └── MainWindowController.xib │ ├── BytesToString.swift │ ├── DropView.swift │ ├── GitHubUpdater.swift │ ├── MainWindowController.swift │ ├── NSView.swift │ ├── Progress.swift │ └── RuntimeError.swift ├── Info.plist ├── PhotoConverter-Bridging-Header.h └── PhotoConverter.entitlements └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: macmade 2 | -------------------------------------------------------------------------------- /.github/workflows/ci-mac.yaml: -------------------------------------------------------------------------------- 1 | name: ci-mac 2 | on: [push] 3 | jobs: 4 | ci: 5 | runs-on: macos-latest 6 | strategy: 7 | matrix: 8 | run-config: 9 | - { scheme: 'Photo Converter', configuration: 'Debug', project: 'PhotoConverter.xcodeproj', build: 1, analyze: 1, test: 0, info: 1, destination: 'platform=macOS' } 10 | - { scheme: 'Photo Converter', configuration: 'Release', project: 'PhotoConverter.xcodeproj', build: 1, analyze: 1, test: 0, info: 1, destination: 'platform=macOS' } 11 | steps: 12 | 13 | - uses: actions/checkout@v1 14 | with: 15 | submodules: 'recursive' 16 | 17 | - uses: macmade/action-xcodebuild@v1.0.0 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac Finder 2 | .DS_Store 3 | .Trashes 4 | Icon? 5 | 6 | # Thumbnails 7 | ._* 8 | 9 | # Files that might appear on external disk 10 | .Spotlight-V100 11 | .Trashes 12 | 13 | # Xcode 14 | *.pbxuser 15 | *.mode1v3 16 | *.mode2v3 17 | *.perspectivev3 18 | *.xccheckout 19 | *.profraw 20 | !default.pbxuser 21 | !default.mode1v3 22 | !default.mode2v3 23 | !default.perspectivev3 24 | xcuserdata 25 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Submodules/xcconfig"] 2 | path = Submodules/xcconfig 3 | url = https://github.com/macmade/xcconfig.git 4 | -------------------------------------------------------------------------------- /Assets/Icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/PhotoConverter/9c3a145f2337d5bbdac9b80373f4e5d563414c97/Assets/Icon.psd -------------------------------------------------------------------------------- /Assets/Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/PhotoConverter/9c3a145f2337d5bbdac9b80373f4e5d563414c97/Assets/Screenshot.png -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | xs-labs.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Jean-David Gadina - www.xs-labs.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /PhotoConverter.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 051A78A22C70D993006328DF /* GitHubUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = 051A78A12C70D993006328DF /* GitHubUpdater.swift */; }; 11 | 055FBBA72C6F82B7003B51AC /* NSView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 055FBBA62C6F82B7003B51AC /* NSView.swift */; }; 12 | 0562F8ED2C7555030012CC94 /* DropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0562F8EC2C7555030012CC94 /* DropView.swift */; }; 13 | 05646BB42685020A00707676 /* ApplicationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05646BAF2685020A00707676 /* ApplicationDelegate.swift */; }; 14 | 05646BB52685020A00707676 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 05646BB12685020A00707676 /* MainMenu.xib */; }; 15 | 05646BEF2685055F00707676 /* AboutWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05646BEE2685055F00707676 /* AboutWindowController.swift */; }; 16 | 05646BF22685058700707676 /* AboutWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 05646BF02685058700707676 /* AboutWindowController.xib */; }; 17 | 05B999772C6ED45500DF4C08 /* MainWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05B999762C6ED45500DF4C08 /* MainWindowController.swift */; }; 18 | 05B9997C2C6ED4BD00DF4C08 /* MainWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 05B9997B2C6ED4BD00DF4C08 /* MainWindowController.xib */; }; 19 | 05B9997F2C6ED8D000DF4C08 /* RuntimeError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05B9997E2C6ED8D000DF4C08 /* RuntimeError.swift */; }; 20 | 05B999952C6F268E00DF4C08 /* BytesToString.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05B999942C6F268E00DF4C08 /* BytesToString.swift */; }; 21 | 05CB9D212C6F48F800314AAC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 05CB9D202C6F48F800314AAC /* Assets.xcassets */; }; 22 | 05F479B12C753A74008B5671 /* Progress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05F479B02C753A74008B5671 /* Progress.swift */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXCopyFilesBuildPhase section */ 26 | 0566B96F2686B531006EE45A /* Embed Frameworks */ = { 27 | isa = PBXCopyFilesBuildPhase; 28 | buildActionMask = 2147483647; 29 | dstPath = ""; 30 | dstSubfolderSpec = 10; 31 | files = ( 32 | ); 33 | name = "Embed Frameworks"; 34 | runOnlyForDeploymentPostprocessing = 0; 35 | }; 36 | /* End PBXCopyFilesBuildPhase section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 051A78A12C70D993006328DF /* GitHubUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GitHubUpdater.swift; sourceTree = ""; }; 40 | 055FBBA62C6F82B7003B51AC /* NSView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSView.swift; sourceTree = ""; }; 41 | 0562F8EC2C7555030012CC94 /* DropView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropView.swift; sourceTree = ""; }; 42 | 05646B9A268501B300707676 /* Photo Converter.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Photo Converter.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | 05646BA4268501B400707676 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 44 | 05646BA5268501B400707676 /* PhotoConverter.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PhotoConverter.entitlements; sourceTree = ""; }; 45 | 05646BAF2685020A00707676 /* ApplicationDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationDelegate.swift; sourceTree = ""; }; 46 | 05646BB22685020A00707676 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 47 | 05646BB8268503D300707676 /* Release - ccache.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Release - ccache.xcconfig"; sourceTree = ""; }; 48 | 05646BB9268503D300707676 /* Common.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Common.xcconfig; sourceTree = ""; }; 49 | 05646BBA268503D300707676 /* Debug - ccache.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Debug - ccache.xcconfig"; sourceTree = ""; }; 50 | 05646BBB268503D300707676 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; 51 | 05646BBC268503D300707676 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 52 | 05646BBD268503D300707676 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 53 | 05646BBE268503D300707676 /* Release - Library.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Release - Library.xcconfig"; sourceTree = ""; }; 54 | 05646BC0268503D300707676 /* Signing.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Signing.xcconfig; sourceTree = ""; }; 55 | 05646BC1268503D300707676 /* Architectures.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Architectures.xcconfig; sourceTree = ""; }; 56 | 05646BC3268503D300707676 /* Issues.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Issues.xcconfig; sourceTree = ""; }; 57 | 05646BC5268503D300707676 /* Apple-APIs.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Apple-APIs.xcconfig"; sourceTree = ""; }; 58 | 05646BC6268503D300707676 /* Generic-Issues.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Generic-Issues.xcconfig"; sourceTree = ""; }; 59 | 05646BC7268503D300707676 /* Security.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Security.xcconfig; sourceTree = ""; }; 60 | 05646BC8268503D300707676 /* Objective-C.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Objective-C.xcconfig"; sourceTree = ""; }; 61 | 05646BC9268503D300707676 /* Analysis-Policy.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Analysis-Policy.xcconfig"; sourceTree = ""; }; 62 | 05646BCA268503D300707676 /* Deployment.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Deployment.xcconfig; sourceTree = ""; }; 63 | 05646BCB268503D300707676 /* Build-Options.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Build-Options.xcconfig"; sourceTree = ""; }; 64 | 05646BCC268503D300707676 /* Swift-Compiler.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Swift-Compiler.xcconfig"; sourceTree = ""; }; 65 | 05646BCD268503D300707676 /* Static-Analyzer.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Static-Analyzer.xcconfig"; sourceTree = ""; }; 66 | 05646BCF268503D300707676 /* Warnings-Policies.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Warnings-Policies.xcconfig"; sourceTree = ""; }; 67 | 05646BD0268503D300707676 /* Code-Generation.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Code-Generation.xcconfig"; sourceTree = ""; }; 68 | 05646BD1268503D300707676 /* Language.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Language.xcconfig; sourceTree = ""; }; 69 | 05646BD2268503D300707676 /* General.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = General.xcconfig; sourceTree = ""; }; 70 | 05646BD3268503D300707676 /* Search-Paths.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Search-Paths.xcconfig"; sourceTree = ""; }; 71 | 05646BD4268503D300707676 /* Apple-LLVM.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Apple-LLVM.xcconfig"; sourceTree = ""; }; 72 | 05646BD7268503D300707676 /* Modules.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Modules.xcconfig; sourceTree = ""; }; 73 | 05646BD8268503D300707676 /* Objective-C.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Objective-C.xcconfig"; sourceTree = ""; }; 74 | 05646BD9268503D300707676 /* C++.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "C++.xcconfig"; sourceTree = ""; }; 75 | 05646BDA268503D300707676 /* Code-Generation.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Code-Generation.xcconfig"; sourceTree = ""; }; 76 | 05646BDB268503D300707676 /* Address-Sanitizer.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Address-Sanitizer.xcconfig"; sourceTree = ""; }; 77 | 05646BDC268503D300707676 /* Language.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Language.xcconfig; sourceTree = ""; }; 78 | 05646BDD268503D300707676 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 79 | 05646BDE268503D300707676 /* Undefined-Behavior-Sanitizer.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Undefined-Behavior-Sanitizer.xcconfig"; sourceTree = ""; }; 80 | 05646BDF268503D300707676 /* Warning-Policies.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Warning-Policies.xcconfig"; sourceTree = ""; }; 81 | 05646BE1268503D300707676 /* Objective-C-ARC.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Objective-C-ARC.xcconfig"; sourceTree = ""; }; 82 | 05646BE2268503D300707676 /* Objective-C.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Objective-C.xcconfig"; sourceTree = ""; }; 83 | 05646BE3268503D300707676 /* C++.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "C++.xcconfig"; sourceTree = ""; }; 84 | 05646BE4268503D300707676 /* All-Languages.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "All-Languages.xcconfig"; sourceTree = ""; }; 85 | 05646BE5268503D300707676 /* Preprocessing.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Preprocessing.xcconfig; sourceTree = ""; }; 86 | 05646BE6268503D300707676 /* Debug - Library.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Debug - Library.xcconfig"; sourceTree = ""; }; 87 | 05646BE7268503D300707676 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = ""; }; 88 | 05646BE9268503D300707676 /* ccache-config.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "ccache-config.sh"; sourceTree = ""; }; 89 | 05646BEA268503D300707676 /* ccache.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ccache.sh; sourceTree = ""; }; 90 | 05646BEB268503D300707676 /* Debug - zld.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Debug - zld.xcconfig"; sourceTree = ""; }; 91 | 05646BEE2685055F00707676 /* AboutWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutWindowController.swift; sourceTree = ""; }; 92 | 05646BF12685058700707676 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/AboutWindowController.xib; sourceTree = ""; }; 93 | 05646C03268527E400707676 /* PhotoConverter-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "PhotoConverter-Bridging-Header.h"; sourceTree = ""; }; 94 | 05B999762C6ED45500DF4C08 /* MainWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainWindowController.swift; sourceTree = ""; }; 95 | 05B9997A2C6ED4BD00DF4C08 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainWindowController.xib; sourceTree = ""; }; 96 | 05B9997E2C6ED8D000DF4C08 /* RuntimeError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeError.swift; sourceTree = ""; }; 97 | 05B999942C6F268E00DF4C08 /* BytesToString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BytesToString.swift; sourceTree = ""; }; 98 | 05CB9D202C6F48F800314AAC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 99 | 05CB9D3B2C6F4AB100314AAC /* CODE_OF_CONDUCT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CODE_OF_CONDUCT.md; sourceTree = ""; }; 100 | 05CB9D3C2C6F4AB100314AAC /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; 101 | 05CB9D3D2C6F4AB100314AAC /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 102 | 05F479B02C753A74008B5671 /* Progress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Progress.swift; sourceTree = ""; }; 103 | /* End PBXFileReference section */ 104 | 105 | /* Begin PBXFrameworksBuildPhase section */ 106 | 05646B97268501B300707676 /* Frameworks */ = { 107 | isa = PBXFrameworksBuildPhase; 108 | buildActionMask = 2147483647; 109 | files = ( 110 | ); 111 | runOnlyForDeploymentPostprocessing = 0; 112 | }; 113 | /* End PBXFrameworksBuildPhase section */ 114 | 115 | /* Begin PBXGroup section */ 116 | 05646B91268501B300707676 = { 117 | isa = PBXGroup; 118 | children = ( 119 | 05CB9D3D2C6F4AB100314AAC /* README.md */, 120 | 05CB9D3C2C6F4AB100314AAC /* LICENSE */, 121 | 05CB9D3B2C6F4AB100314AAC /* CODE_OF_CONDUCT.md */, 122 | 05646BB7268503D300707676 /* xcconfig */, 123 | 05646B9C268501B300707676 /* PhotoConverter */, 124 | 05646B9B268501B300707676 /* Products */, 125 | 0566B96C2686B531006EE45A /* Frameworks */, 126 | ); 127 | sourceTree = ""; 128 | }; 129 | 05646B9B268501B300707676 /* Products */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | 05646B9A268501B300707676 /* Photo Converter.app */, 133 | ); 134 | name = Products; 135 | sourceTree = ""; 136 | }; 137 | 05646B9C268501B300707676 /* PhotoConverter */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | 05646BAD2685020A00707676 /* Classes */, 141 | 05646BB62685021500707676 /* Supporting Files */, 142 | ); 143 | path = PhotoConverter; 144 | sourceTree = ""; 145 | }; 146 | 05646BAD2685020A00707676 /* Classes */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | 05646BEE2685055F00707676 /* AboutWindowController.swift */, 150 | 05646BF02685058700707676 /* AboutWindowController.xib */, 151 | 05646BAF2685020A00707676 /* ApplicationDelegate.swift */, 152 | 05B999942C6F268E00DF4C08 /* BytesToString.swift */, 153 | 0562F8EC2C7555030012CC94 /* DropView.swift */, 154 | 051A78A12C70D993006328DF /* GitHubUpdater.swift */, 155 | 05B999762C6ED45500DF4C08 /* MainWindowController.swift */, 156 | 05B9997B2C6ED4BD00DF4C08 /* MainWindowController.xib */, 157 | 055FBBA62C6F82B7003B51AC /* NSView.swift */, 158 | 05F479B02C753A74008B5671 /* Progress.swift */, 159 | 05B9997E2C6ED8D000DF4C08 /* RuntimeError.swift */, 160 | ); 161 | path = Classes; 162 | sourceTree = ""; 163 | }; 164 | 05646BB62685021500707676 /* Supporting Files */ = { 165 | isa = PBXGroup; 166 | children = ( 167 | 05CB9D202C6F48F800314AAC /* Assets.xcassets */, 168 | 05646BA4268501B400707676 /* Info.plist */, 169 | 05646BB12685020A00707676 /* MainMenu.xib */, 170 | 05646C03268527E400707676 /* PhotoConverter-Bridging-Header.h */, 171 | 05646BA5268501B400707676 /* PhotoConverter.entitlements */, 172 | ); 173 | name = "Supporting Files"; 174 | sourceTree = ""; 175 | }; 176 | 05646BB7268503D300707676 /* xcconfig */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | 05646BB8268503D300707676 /* Release - ccache.xcconfig */, 180 | 05646BB9268503D300707676 /* Common.xcconfig */, 181 | 05646BBA268503D300707676 /* Debug - ccache.xcconfig */, 182 | 05646BBB268503D300707676 /* Debug.xcconfig */, 183 | 05646BBC268503D300707676 /* Release.xcconfig */, 184 | 05646BBD268503D300707676 /* README.md */, 185 | 05646BBE268503D300707676 /* Release - Library.xcconfig */, 186 | 05646BBF268503D300707676 /* Common */, 187 | 05646BE6268503D300707676 /* Debug - Library.xcconfig */, 188 | 05646BE7268503D300707676 /* .gitignore */, 189 | 05646BE8268503D300707676 /* Scripts */, 190 | 05646BEB268503D300707676 /* Debug - zld.xcconfig */, 191 | ); 192 | name = xcconfig; 193 | path = Submodules/xcconfig; 194 | sourceTree = ""; 195 | }; 196 | 05646BBF268503D300707676 /* Common */ = { 197 | isa = PBXGroup; 198 | children = ( 199 | 05646BC0268503D300707676 /* Signing.xcconfig */, 200 | 05646BC1268503D300707676 /* Architectures.xcconfig */, 201 | 05646BC2268503D300707676 /* Static-Analyzer */, 202 | 05646BCA268503D300707676 /* Deployment.xcconfig */, 203 | 05646BCB268503D300707676 /* Build-Options.xcconfig */, 204 | 05646BCC268503D300707676 /* Swift-Compiler.xcconfig */, 205 | 05646BCD268503D300707676 /* Static-Analyzer.xcconfig */, 206 | 05646BCE268503D300707676 /* Swift-Compiler */, 207 | 05646BD3268503D300707676 /* Search-Paths.xcconfig */, 208 | 05646BD4268503D300707676 /* Apple-LLVM.xcconfig */, 209 | 05646BD5268503D300707676 /* Apple-LLVM */, 210 | ); 211 | path = Common; 212 | sourceTree = ""; 213 | }; 214 | 05646BC2268503D300707676 /* Static-Analyzer */ = { 215 | isa = PBXGroup; 216 | children = ( 217 | 05646BC3268503D300707676 /* Issues.xcconfig */, 218 | 05646BC4268503D300707676 /* Issues */, 219 | ); 220 | path = "Static-Analyzer"; 221 | sourceTree = ""; 222 | }; 223 | 05646BC4268503D300707676 /* Issues */ = { 224 | isa = PBXGroup; 225 | children = ( 226 | 05646BC5268503D300707676 /* Apple-APIs.xcconfig */, 227 | 05646BC6268503D300707676 /* Generic-Issues.xcconfig */, 228 | 05646BC7268503D300707676 /* Security.xcconfig */, 229 | 05646BC8268503D300707676 /* Objective-C.xcconfig */, 230 | 05646BC9268503D300707676 /* Analysis-Policy.xcconfig */, 231 | ); 232 | path = Issues; 233 | sourceTree = ""; 234 | }; 235 | 05646BCE268503D300707676 /* Swift-Compiler */ = { 236 | isa = PBXGroup; 237 | children = ( 238 | 05646BCF268503D300707676 /* Warnings-Policies.xcconfig */, 239 | 05646BD0268503D300707676 /* Code-Generation.xcconfig */, 240 | 05646BD1268503D300707676 /* Language.xcconfig */, 241 | 05646BD2268503D300707676 /* General.xcconfig */, 242 | ); 243 | path = "Swift-Compiler"; 244 | sourceTree = ""; 245 | }; 246 | 05646BD5268503D300707676 /* Apple-LLVM */ = { 247 | isa = PBXGroup; 248 | children = ( 249 | 05646BD6268503D300707676 /* Language */, 250 | 05646BDA268503D300707676 /* Code-Generation.xcconfig */, 251 | 05646BDB268503D300707676 /* Address-Sanitizer.xcconfig */, 252 | 05646BDC268503D300707676 /* Language.xcconfig */, 253 | 05646BDD268503D300707676 /* Warnings.xcconfig */, 254 | 05646BDE268503D300707676 /* Undefined-Behavior-Sanitizer.xcconfig */, 255 | 05646BDF268503D300707676 /* Warning-Policies.xcconfig */, 256 | 05646BE0268503D300707676 /* Warnings */, 257 | 05646BE5268503D300707676 /* Preprocessing.xcconfig */, 258 | ); 259 | path = "Apple-LLVM"; 260 | sourceTree = ""; 261 | }; 262 | 05646BD6268503D300707676 /* Language */ = { 263 | isa = PBXGroup; 264 | children = ( 265 | 05646BD7268503D300707676 /* Modules.xcconfig */, 266 | 05646BD8268503D300707676 /* Objective-C.xcconfig */, 267 | 05646BD9268503D300707676 /* C++.xcconfig */, 268 | ); 269 | path = Language; 270 | sourceTree = ""; 271 | }; 272 | 05646BE0268503D300707676 /* Warnings */ = { 273 | isa = PBXGroup; 274 | children = ( 275 | 05646BE1268503D300707676 /* Objective-C-ARC.xcconfig */, 276 | 05646BE2268503D300707676 /* Objective-C.xcconfig */, 277 | 05646BE3268503D300707676 /* C++.xcconfig */, 278 | 05646BE4268503D300707676 /* All-Languages.xcconfig */, 279 | ); 280 | path = Warnings; 281 | sourceTree = ""; 282 | }; 283 | 05646BE8268503D300707676 /* Scripts */ = { 284 | isa = PBXGroup; 285 | children = ( 286 | 05646BE9268503D300707676 /* ccache-config.sh */, 287 | 05646BEA268503D300707676 /* ccache.sh */, 288 | ); 289 | path = Scripts; 290 | sourceTree = ""; 291 | }; 292 | 0566B96C2686B531006EE45A /* Frameworks */ = { 293 | isa = PBXGroup; 294 | children = ( 295 | ); 296 | name = Frameworks; 297 | sourceTree = ""; 298 | }; 299 | /* End PBXGroup section */ 300 | 301 | /* Begin PBXNativeTarget section */ 302 | 05646B99268501B300707676 /* Photo Converter */ = { 303 | isa = PBXNativeTarget; 304 | buildConfigurationList = 05646BA8268501B400707676 /* Build configuration list for PBXNativeTarget "Photo Converter" */; 305 | buildPhases = ( 306 | 05646B96268501B300707676 /* Sources */, 307 | 05646B97268501B300707676 /* Frameworks */, 308 | 05646BED268503FE00707676 /* ShellScript */, 309 | 05646B98268501B300707676 /* Resources */, 310 | 0566B96F2686B531006EE45A /* Embed Frameworks */, 311 | ); 312 | buildRules = ( 313 | ); 314 | dependencies = ( 315 | ); 316 | name = "Photo Converter"; 317 | productName = Sensors; 318 | productReference = 05646B9A268501B300707676 /* Photo Converter.app */; 319 | productType = "com.apple.product-type.application"; 320 | }; 321 | /* End PBXNativeTarget section */ 322 | 323 | /* Begin PBXProject section */ 324 | 05646B92268501B300707676 /* Project object */ = { 325 | isa = PBXProject; 326 | attributes = { 327 | LastSwiftUpdateCheck = 1250; 328 | LastUpgradeCheck = 1540; 329 | TargetAttributes = { 330 | 05646B99268501B300707676 = { 331 | CreatedOnToolsVersion = 12.5.1; 332 | LastSwiftMigration = 1250; 333 | }; 334 | }; 335 | }; 336 | buildConfigurationList = 05646B95268501B300707676 /* Build configuration list for PBXProject "PhotoConverter" */; 337 | compatibilityVersion = "Xcode 9.3"; 338 | developmentRegion = en; 339 | hasScannedForEncodings = 0; 340 | knownRegions = ( 341 | en, 342 | Base, 343 | ); 344 | mainGroup = 05646B91268501B300707676; 345 | productRefGroup = 05646B9B268501B300707676 /* Products */; 346 | projectDirPath = ""; 347 | projectRoot = ""; 348 | targets = ( 349 | 05646B99268501B300707676 /* Photo Converter */, 350 | ); 351 | }; 352 | /* End PBXProject section */ 353 | 354 | /* Begin PBXResourcesBuildPhase section */ 355 | 05646B98268501B300707676 /* Resources */ = { 356 | isa = PBXResourcesBuildPhase; 357 | buildActionMask = 2147483647; 358 | files = ( 359 | 05646BB52685020A00707676 /* MainMenu.xib in Resources */, 360 | 05B9997C2C6ED4BD00DF4C08 /* MainWindowController.xib in Resources */, 361 | 05646BF22685058700707676 /* AboutWindowController.xib in Resources */, 362 | 05CB9D212C6F48F800314AAC /* Assets.xcassets in Resources */, 363 | ); 364 | runOnlyForDeploymentPostprocessing = 0; 365 | }; 366 | /* End PBXResourcesBuildPhase section */ 367 | 368 | /* Begin PBXShellScriptBuildPhase section */ 369 | 05646BED268503FE00707676 /* ShellScript */ = { 370 | isa = PBXShellScriptBuildPhase; 371 | alwaysOutOfDate = 1; 372 | buildActionMask = 2147483647; 373 | files = ( 374 | ); 375 | inputFileListPaths = ( 376 | ); 377 | inputPaths = ( 378 | ); 379 | outputFileListPaths = ( 380 | ); 381 | outputPaths = ( 382 | ); 383 | runOnlyForDeploymentPostprocessing = 0; 384 | shellPath = /bin/sh; 385 | shellScript = "#!/bin/bash\nif [ \"${CONFIGURATION}\" = \"Release\" ]; then\n plist=\"PhotoConverter/Info.plist\"\n rev=$(git rev-list `git branch | grep -e \"^*\" | cut -d' ' -f 2` | wc -l)\n /usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $rev\" \"$plist\"\nfi\n"; 386 | }; 387 | /* End PBXShellScriptBuildPhase section */ 388 | 389 | /* Begin PBXSourcesBuildPhase section */ 390 | 05646B96268501B300707676 /* Sources */ = { 391 | isa = PBXSourcesBuildPhase; 392 | buildActionMask = 2147483647; 393 | files = ( 394 | 051A78A22C70D993006328DF /* GitHubUpdater.swift in Sources */, 395 | 05646BB42685020A00707676 /* ApplicationDelegate.swift in Sources */, 396 | 055FBBA72C6F82B7003B51AC /* NSView.swift in Sources */, 397 | 05B999952C6F268E00DF4C08 /* BytesToString.swift in Sources */, 398 | 05646BEF2685055F00707676 /* AboutWindowController.swift in Sources */, 399 | 05B999772C6ED45500DF4C08 /* MainWindowController.swift in Sources */, 400 | 05F479B12C753A74008B5671 /* Progress.swift in Sources */, 401 | 05B9997F2C6ED8D000DF4C08 /* RuntimeError.swift in Sources */, 402 | 0562F8ED2C7555030012CC94 /* DropView.swift in Sources */, 403 | ); 404 | runOnlyForDeploymentPostprocessing = 0; 405 | }; 406 | /* End PBXSourcesBuildPhase section */ 407 | 408 | /* Begin PBXVariantGroup section */ 409 | 05646BB12685020A00707676 /* MainMenu.xib */ = { 410 | isa = PBXVariantGroup; 411 | children = ( 412 | 05646BB22685020A00707676 /* Base */, 413 | ); 414 | name = MainMenu.xib; 415 | sourceTree = ""; 416 | }; 417 | 05646BF02685058700707676 /* AboutWindowController.xib */ = { 418 | isa = PBXVariantGroup; 419 | children = ( 420 | 05646BF12685058700707676 /* Base */, 421 | ); 422 | name = AboutWindowController.xib; 423 | sourceTree = ""; 424 | }; 425 | 05B9997B2C6ED4BD00DF4C08 /* MainWindowController.xib */ = { 426 | isa = PBXVariantGroup; 427 | children = ( 428 | 05B9997A2C6ED4BD00DF4C08 /* Base */, 429 | ); 430 | name = MainWindowController.xib; 431 | sourceTree = ""; 432 | }; 433 | /* End PBXVariantGroup section */ 434 | 435 | /* Begin XCBuildConfiguration section */ 436 | 05646BA6268501B400707676 /* Debug */ = { 437 | isa = XCBuildConfiguration; 438 | baseConfigurationReference = 05646BBB268503D300707676 /* Debug.xcconfig */; 439 | buildSettings = { 440 | MACOSX_DEPLOYMENT_TARGET = 14.0; 441 | }; 442 | name = Debug; 443 | }; 444 | 05646BA7268501B400707676 /* Release */ = { 445 | isa = XCBuildConfiguration; 446 | baseConfigurationReference = 05646BBC268503D300707676 /* Release.xcconfig */; 447 | buildSettings = { 448 | MACOSX_DEPLOYMENT_TARGET = 14.0; 449 | }; 450 | name = Release; 451 | }; 452 | 05646BA9268501B400707676 /* Debug */ = { 453 | isa = XCBuildConfiguration; 454 | buildSettings = { 455 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 456 | ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; 457 | CLANG_ENABLE_MODULES = YES; 458 | CODE_SIGN_ENTITLEMENTS = PhotoConverter/PhotoConverter.entitlements; 459 | CODE_SIGN_STYLE = Automatic; 460 | DEVELOPMENT_TEAM = 326Y53CJMD; 461 | ENABLE_HARDENED_RUNTIME = YES; 462 | INFOPLIST_FILE = PhotoConverter/Info.plist; 463 | LD_RUNPATH_SEARCH_PATHS = ( 464 | "$(inherited)", 465 | "@executable_path/../Frameworks", 466 | ); 467 | PRODUCT_BUNDLE_IDENTIFIER = "com.xs-labs.PhotoConverter"; 468 | PRODUCT_NAME = "Photo Converter"; 469 | SWIFT_OBJC_BRIDGING_HEADER = "PhotoConverter/PhotoConverter-Bridging-Header.h"; 470 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 471 | SWIFT_VERSION = 5.0; 472 | }; 473 | name = Debug; 474 | }; 475 | 05646BAA268501B400707676 /* Release */ = { 476 | isa = XCBuildConfiguration; 477 | buildSettings = { 478 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 479 | ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; 480 | CLANG_ENABLE_MODULES = YES; 481 | CODE_SIGN_ENTITLEMENTS = PhotoConverter/PhotoConverter.entitlements; 482 | CODE_SIGN_STYLE = Automatic; 483 | DEVELOPMENT_TEAM = 326Y53CJMD; 484 | ENABLE_HARDENED_RUNTIME = YES; 485 | INFOPLIST_FILE = PhotoConverter/Info.plist; 486 | LD_RUNPATH_SEARCH_PATHS = ( 487 | "$(inherited)", 488 | "@executable_path/../Frameworks", 489 | ); 490 | PRODUCT_BUNDLE_IDENTIFIER = "com.xs-labs.PhotoConverter"; 491 | PRODUCT_NAME = "Photo Converter"; 492 | SWIFT_OBJC_BRIDGING_HEADER = "PhotoConverter/PhotoConverter-Bridging-Header.h"; 493 | SWIFT_VERSION = 5.0; 494 | }; 495 | name = Release; 496 | }; 497 | /* End XCBuildConfiguration section */ 498 | 499 | /* Begin XCConfigurationList section */ 500 | 05646B95268501B300707676 /* Build configuration list for PBXProject "PhotoConverter" */ = { 501 | isa = XCConfigurationList; 502 | buildConfigurations = ( 503 | 05646BA6268501B400707676 /* Debug */, 504 | 05646BA7268501B400707676 /* Release */, 505 | ); 506 | defaultConfigurationIsVisible = 0; 507 | defaultConfigurationName = Release; 508 | }; 509 | 05646BA8268501B400707676 /* Build configuration list for PBXNativeTarget "Photo Converter" */ = { 510 | isa = XCConfigurationList; 511 | buildConfigurations = ( 512 | 05646BA9268501B400707676 /* Debug */, 513 | 05646BAA268501B400707676 /* Release */, 514 | ); 515 | defaultConfigurationIsVisible = 0; 516 | defaultConfigurationName = Release; 517 | }; 518 | /* End XCConfigurationList section */ 519 | }; 520 | rootObject = 05646B92268501B300707676 /* Project object */; 521 | } 522 | -------------------------------------------------------------------------------- /PhotoConverter.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PhotoConverter.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /PhotoConverter.xcodeproj/xcshareddata/xcschemes/Photo Converter.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /PhotoConverter/Assets.xcassets/AppIcon.appiconset/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/PhotoConverter/9c3a145f2337d5bbdac9b80373f4e5d563414c97/PhotoConverter/Assets.xcassets/AppIcon.appiconset/128.png -------------------------------------------------------------------------------- /PhotoConverter/Assets.xcassets/AppIcon.appiconset/128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/PhotoConverter/9c3a145f2337d5bbdac9b80373f4e5d563414c97/PhotoConverter/Assets.xcassets/AppIcon.appiconset/128@2x.png -------------------------------------------------------------------------------- /PhotoConverter/Assets.xcassets/AppIcon.appiconset/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/PhotoConverter/9c3a145f2337d5bbdac9b80373f4e5d563414c97/PhotoConverter/Assets.xcassets/AppIcon.appiconset/16.png -------------------------------------------------------------------------------- /PhotoConverter/Assets.xcassets/AppIcon.appiconset/16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/PhotoConverter/9c3a145f2337d5bbdac9b80373f4e5d563414c97/PhotoConverter/Assets.xcassets/AppIcon.appiconset/16@2x.png -------------------------------------------------------------------------------- /PhotoConverter/Assets.xcassets/AppIcon.appiconset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/PhotoConverter/9c3a145f2337d5bbdac9b80373f4e5d563414c97/PhotoConverter/Assets.xcassets/AppIcon.appiconset/256.png -------------------------------------------------------------------------------- /PhotoConverter/Assets.xcassets/AppIcon.appiconset/256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/PhotoConverter/9c3a145f2337d5bbdac9b80373f4e5d563414c97/PhotoConverter/Assets.xcassets/AppIcon.appiconset/256@2x.png -------------------------------------------------------------------------------- /PhotoConverter/Assets.xcassets/AppIcon.appiconset/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/PhotoConverter/9c3a145f2337d5bbdac9b80373f4e5d563414c97/PhotoConverter/Assets.xcassets/AppIcon.appiconset/32.png -------------------------------------------------------------------------------- /PhotoConverter/Assets.xcassets/AppIcon.appiconset/32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/PhotoConverter/9c3a145f2337d5bbdac9b80373f4e5d563414c97/PhotoConverter/Assets.xcassets/AppIcon.appiconset/32@2x.png -------------------------------------------------------------------------------- /PhotoConverter/Assets.xcassets/AppIcon.appiconset/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/PhotoConverter/9c3a145f2337d5bbdac9b80373f4e5d563414c97/PhotoConverter/Assets.xcassets/AppIcon.appiconset/512.png -------------------------------------------------------------------------------- /PhotoConverter/Assets.xcassets/AppIcon.appiconset/512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/macmade/PhotoConverter/9c3a145f2337d5bbdac9b80373f4e5d563414c97/PhotoConverter/Assets.xcassets/AppIcon.appiconset/512@2x.png -------------------------------------------------------------------------------- /PhotoConverter/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "16@2x.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "32@2x.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "128@2x.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "256@2x.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "512@2x.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /PhotoConverter/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /PhotoConverter/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 | 419 | 420 | 421 | 422 | 423 | -------------------------------------------------------------------------------- /PhotoConverter/Classes/AboutWindowController.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Cocoa 26 | 27 | public class AboutWindowController: NSWindowController 28 | { 29 | @objc private dynamic var name: String? 30 | @objc private dynamic var version: String? 31 | @objc private dynamic var copyright: String? 32 | 33 | public override var windowNibName: NSNib.Name? 34 | { 35 | "AboutWindowController" 36 | } 37 | 38 | public override func windowDidLoad() 39 | { 40 | super.windowDidLoad() 41 | 42 | let version = Bundle.main.object( forInfoDictionaryKey: "CFBundleShortVersionString" ) as? String ?? "0.0.0" 43 | 44 | if let build = Bundle.main.object( forInfoDictionaryKey: "CFBundleVersion" ) as? String 45 | { 46 | self.version = "\( version ) (\( build ))" 47 | } 48 | else 49 | { 50 | self.version = version 51 | } 52 | 53 | self.name = Bundle.main.object( forInfoDictionaryKey: "CFBundleName" ) as? String 54 | self.copyright = Bundle.main.object( forInfoDictionaryKey: "NSHumanReadableCopyright" ) as? String 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /PhotoConverter/Classes/ApplicationDelegate.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Cocoa 26 | 27 | @main 28 | public class ApplicationDelegate: NSObject, NSApplicationDelegate, NSWindowDelegate 29 | { 30 | @objc private dynamic var aboutWindowController = AboutWindowController() 31 | @objc private dynamic var windowControllers = [ MainWindowController ]() 32 | 33 | private var updater = GitHubUpdater( owner: "macmade", repository: "PhotoConverter" ) 34 | 35 | public func applicationDidFinishLaunching( _ notification: Notification ) 36 | { 37 | self.openDocument( nil ) 38 | 39 | DispatchQueue.main.asyncAfter( deadline: .now() + .seconds( 2 ) ) 40 | { 41 | self.updater?.checkForUpdates() 42 | } 43 | } 44 | 45 | public func applicationWillTerminate( _ notification: Notification ) 46 | {} 47 | 48 | public func applicationShouldTerminateAfterLastWindowClosed( _ sender: NSApplication ) -> Bool 49 | { 50 | false 51 | } 52 | 53 | public func applicationSupportsSecureRestorableState( _ app: NSApplication ) -> Bool 54 | { 55 | false 56 | } 57 | 58 | @IBAction 59 | public func showAboutWindow( _ sender: Any? ) 60 | { 61 | if self.aboutWindowController.window?.isVisible == false 62 | { 63 | self.aboutWindowController.window?.layoutIfNeeded() 64 | self.aboutWindowController.window?.center() 65 | } 66 | 67 | self.aboutWindowController.window?.makeKeyAndOrderFront( sender ) 68 | } 69 | 70 | @IBAction 71 | public func newDocument( _ sender: Any? ) 72 | { 73 | self.openDocument( sender ) 74 | } 75 | 76 | @IBAction 77 | public func openDocument( _ sender: Any? ) 78 | { 79 | let panel = NSOpenPanel() 80 | panel.canChooseFiles = false 81 | panel.canChooseDirectories = true 82 | panel.allowsMultipleSelection = false 83 | 84 | guard panel.runModal() == .OK, let url = panel.url 85 | else 86 | { 87 | return 88 | } 89 | 90 | let controller = MainWindowController( url: url ) 91 | controller.onClose = 92 | { 93 | [ weak self ] in self?.windowControllers.removeAll 94 | { 95 | $0 === controller 96 | } 97 | } 98 | 99 | self.windowControllers.append( controller ) 100 | controller.window?.makeKeyAndOrderFront( sender ) 101 | } 102 | 103 | public func applicationShouldTerminate( _ sender: NSApplication ) -> NSApplication.TerminateReply 104 | { 105 | for controller in self.windowControllers 106 | { 107 | if controller.windowCanBeClosed() == false 108 | { 109 | return .terminateCancel 110 | } 111 | } 112 | 113 | return .terminateNow 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /PhotoConverter/Classes/Base.lproj/AboutWindowController.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 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /PhotoConverter/Classes/Base.lproj/MainWindowController.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 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | NSNegateBoolean 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | NSIsNotNil 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | NSIsNil 82 | 83 | 84 | 85 | 86 | NSIsNotNil 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | NSIsNil 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | NSNegateBoolean 170 | 171 | 172 | 173 | 174 | 175 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | -------------------------------------------------------------------------------- /PhotoConverter/Classes/BytesToString.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Foundation 26 | 27 | @objc( BytesToString ) 28 | public class BytesToString: ValueTransformer 29 | { 30 | public class func string( from bytes: Int64 ) -> String 31 | { 32 | if bytes < 1000 33 | { 34 | return "\( bytes ) bytes" 35 | } 36 | else if bytes < 1000 * 1000 37 | { 38 | return String( format: "%.02f KB", Double( bytes ) / 1000.0 ) 39 | } 40 | else if bytes < 1000 * 1000 * 1000 41 | { 42 | return String( format: "%.02f MB", ( Double( bytes ) / 1000.0 ) / 1000.0 ) 43 | } 44 | 45 | return String( format: "%.02f GB", ( ( Double( bytes ) / 1000.0 ) / 1000.0 ) / 1000.0 ) 46 | } 47 | 48 | public override class func transformedValueClass() -> AnyClass 49 | { 50 | NSString.self 51 | } 52 | 53 | public override class func allowsReverseTransformation() -> Bool 54 | { 55 | false 56 | } 57 | 58 | public override func transformedValue( _ value: Any? ) -> Any? 59 | { 60 | guard let bytes = value as? Int64 61 | else 62 | { 63 | return nil 64 | } 65 | 66 | return BytesToString.string( from: bytes ) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /PhotoConverter/Classes/DropView.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Cocoa 26 | 27 | @objc 28 | public class DropView: NSView 29 | { 30 | public var onDrop: ( ( URL ) -> Void )? 31 | 32 | public override init( frame: NSRect ) 33 | { 34 | super.init( frame: frame ) 35 | self.registerForDraggedTypes( [ .fileURL ] ) 36 | } 37 | 38 | public required init?( coder: NSCoder ) 39 | { 40 | super.init( coder: coder ) 41 | self.registerForDraggedTypes( [ .fileURL ] ) 42 | } 43 | 44 | public override func draggingEntered( _ sender: any NSDraggingInfo ) -> NSDragOperation 45 | { 46 | var isDir = ObjCBool( false ) 47 | 48 | guard let url = sender.draggingPasteboard.readObjects( forClasses: [ NSURL.self ] )?.first as? URL, 49 | FileManager.default.fileExists( atPath: url.path, isDirectory: &isDir ), 50 | isDir.boolValue 51 | else 52 | { 53 | return [] 54 | } 55 | 56 | return .copy 57 | } 58 | 59 | public override func performDragOperation( _ sender: any NSDraggingInfo ) -> Bool 60 | { 61 | var isDir = ObjCBool( false ) 62 | 63 | guard let action = self.onDrop, 64 | let url = sender.draggingPasteboard.readObjects( forClasses: [ NSURL.self ] )?.first as? URL, 65 | FileManager.default.fileExists( atPath: url.path, isDirectory: &isDir ), 66 | isDir.boolValue 67 | else 68 | { 69 | return false 70 | } 71 | 72 | action( url ) 73 | 74 | return true 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /PhotoConverter/Classes/GitHubUpdater.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Cocoa 26 | 27 | public class GitHubUpdater 28 | { 29 | public private( set ) var owner: String 30 | public private( set ) var repository: String 31 | public private( set ) var url: URL 32 | 33 | public init?( owner: String, repository: String ) 34 | { 35 | guard let url = URL( string: "https://api.github.com/repos/\( owner )/\( repository )/releases" ) 36 | else 37 | { 38 | return nil 39 | } 40 | 41 | self.owner = owner 42 | self.repository = repository 43 | self.url = url 44 | } 45 | 46 | public func checkForUpdates() 47 | { 48 | DispatchQueue.global( qos: .background ).async 49 | { 50 | Task 51 | { 52 | guard let ( data, _ ) = try? await URLSession.shared.data( from: self.url ) 53 | else 54 | { 55 | return 56 | } 57 | 58 | guard let releases = try? JSONSerialization.jsonObject( with: data ) as? [ [ String: Any ] ] 59 | else 60 | { 61 | return 62 | } 63 | 64 | let versions: [ ( version: String, url: String ) ] = releases.compactMap 65 | { 66 | guard let version = $0[ "tag_name" ] as? String, 67 | let url = $0[ "html_url" ] as? String 68 | else 69 | { 70 | return nil 71 | } 72 | 73 | return ( version: version, url: url ) 74 | } 75 | .sorted 76 | { 77 | $0.version.compare( $1.version, options: .numeric ) == .orderedDescending 78 | } 79 | 80 | guard let latest = versions.first, 81 | let current = Bundle.main.object( forInfoDictionaryKey: "CFBundleShortVersionString" ) as? String, 82 | let program = Bundle.main.object( forInfoDictionaryKey: "CFBundleName" ) as? String 83 | else 84 | { 85 | return 86 | } 87 | 88 | if current.compare( latest.version, options: .numeric ) == .orderedAscending 89 | { 90 | guard let url = URL( string: latest.url ) 91 | else 92 | { 93 | return 94 | } 95 | 96 | DispatchQueue.main.async 97 | { 98 | let alert = NSAlert() 99 | alert.messageText = "Update Available" 100 | alert.informativeText = "\( program ) \( latest.version ) is available.\nYou are currently on version \( current ).\n\nWould you like to download the new version?" 101 | 102 | alert.addButton( withTitle: "View and Download" ) 103 | alert.addButton( withTitle: "Later" ) 104 | 105 | if alert.runModal() == .alertFirstButtonReturn 106 | { 107 | NSWorkspace.shared.open( url ) 108 | } 109 | } 110 | } 111 | } 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /PhotoConverter/Classes/MainWindowController.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Cocoa 26 | import UniformTypeIdentifiers 27 | 28 | @objc 29 | public class MainWindowController: NSWindowController, NSWindowDelegate 30 | { 31 | @objc private dynamic var url: URL 32 | @objc private dynamic var name: String? 33 | @objc private dynamic var icon: NSImage? 34 | @objc private dynamic var info: String? 35 | @objc private dynamic var progress: Progress? 36 | @objc private dynamic var loading = false 37 | @objc private dynamic var format = 0 38 | 39 | private var images: [ URL ] = [] 40 | private let queue = OperationQueue() 41 | 42 | @IBOutlet private var dropView: DropView? 43 | 44 | public var onClose: ( () -> Void )? 45 | 46 | private enum ImageFormat: Int 47 | { 48 | case tif 49 | case png 50 | case jpg 51 | case bmp 52 | case gif 53 | } 54 | 55 | public init( url: URL ) 56 | { 57 | self.url = url 58 | 59 | super.init( window: nil ) 60 | } 61 | 62 | public required init?( coder: NSCoder ) 63 | { 64 | nil 65 | } 66 | 67 | public override var windowNibName: NSNib.Name? 68 | { 69 | "MainWindowController" 70 | } 71 | 72 | public override func windowDidLoad() 73 | { 74 | super.windowDidLoad() 75 | self.loadImages() 76 | 77 | self.queue.qualityOfService = .userInitiated 78 | self.queue.maxConcurrentOperationCount = 20 79 | 80 | if let view = self.dropView 81 | { 82 | view.onDrop = 83 | { 84 | guard self.loading == false 85 | else 86 | { 87 | NSSound.beep() 88 | 89 | return 90 | } 91 | 92 | self.url = $0 93 | 94 | self.loadImages() 95 | } 96 | } 97 | } 98 | 99 | public func windowCanBeClosed() -> Bool 100 | { 101 | guard self.loading 102 | else 103 | { 104 | return true 105 | } 106 | 107 | let alert = NSAlert() 108 | alert.messageText = "Operations in Progress" 109 | alert.informativeText = "Are you sure you really want to close this window?\n\nThis will abort all running operations." 110 | 111 | alert.addButton( withTitle: "Cancel" ) 112 | alert.addButton( withTitle: "Close Anyway" ) 113 | 114 | if alert.runModal() == .alertFirstButtonReturn 115 | { 116 | return false 117 | } 118 | else 119 | { 120 | self.queue.cancelAllOperations() 121 | 122 | return true 123 | } 124 | } 125 | 126 | public func windowShouldClose( _ sender: NSWindow ) -> Bool 127 | { 128 | self.windowCanBeClosed() 129 | } 130 | 131 | public func windowWillClose( _ notification: Notification ) 132 | { 133 | self.onClose?() 134 | } 135 | 136 | private func showError( message: String, closeWindow: Bool ) 137 | { 138 | DispatchQueue.main.async 139 | { 140 | let alert = NSAlert() 141 | alert.messageText = "Error" 142 | alert.informativeText = message 143 | 144 | if let window = self.window 145 | { 146 | alert.beginSheetModal( for: window ) 147 | { 148 | _ in if closeWindow 149 | { 150 | DispatchQueue.main.async 151 | { 152 | window.performClose( nil ) 153 | } 154 | } 155 | } 156 | } 157 | else 158 | { 159 | alert.runModal() 160 | 161 | if closeWindow 162 | { 163 | DispatchQueue.main.async 164 | { 165 | self.window?.performClose( nil ) 166 | } 167 | } 168 | } 169 | } 170 | } 171 | 172 | private func loadImages() 173 | { 174 | self.images = [] 175 | self.window?.title = "Photo Converter - \( self.url.lastPathComponent )" 176 | self.name = self.url.lastPathComponent 177 | self.icon = NSWorkspace.shared.icon( forFile: self.url.path( percentEncoded: false ) ) 178 | self.loading = true 179 | self.info = "Loading Images - Please Wait..." 180 | 181 | DispatchQueue.global( qos: .userInitiated ).async 182 | { 183 | guard let enumerator = FileManager.default.enumerator( atPath: self.url.path( percentEncoded: false ) ) 184 | else 185 | { 186 | self.showError( message: "Cannot read directory: \( self.url.lastPathComponent )", closeWindow: true ) 187 | 188 | return 189 | } 190 | 191 | let files: [ ( url: URL, size: Int64 ) ] = enumerator.compactMap 192 | { 193 | enumerator.skipDescendants() 194 | 195 | guard let name = $0 as? String 196 | else 197 | { 198 | return nil 199 | } 200 | 201 | let url = self.url.appending( component: name ) 202 | 203 | guard let uti = UTType( filenameExtension: url.pathExtension ), 204 | uti.conforms( to: .image ) 205 | else 206 | { 207 | return nil 208 | } 209 | 210 | guard let info = try? url.resourceValues( forKeys: [ .fileSizeKey ] ), 211 | let size = info.fileSize, 212 | size > 0 213 | else 214 | { 215 | return nil 216 | } 217 | 218 | return ( url, Int64( size ) ) 219 | } 220 | 221 | let size = files.reduce( 0 ) 222 | { 223 | $0 + $1.size 224 | } 225 | 226 | self.images = files.map 227 | { 228 | $0.url 229 | } 230 | 231 | DispatchQueue.main.async 232 | { 233 | guard files.isEmpty == false 234 | else 235 | { 236 | self.showError( message: "The selected directory doesn't contain any image.", closeWindow: true ) 237 | 238 | return 239 | } 240 | 241 | self.loading = false 242 | self.info = "\( files.count ) images - \( BytesToString.string( from: size ) )" 243 | } 244 | } 245 | } 246 | 247 | @IBAction 248 | private func convert( _ sender: Any? ) 249 | { 250 | guard let window = self.window, 251 | let format = ImageFormat( rawValue: self.format ) 252 | else 253 | { 254 | NSSound.beep() 255 | 256 | return 257 | } 258 | 259 | let panel = NSOpenPanel() 260 | panel.canChooseFiles = false 261 | panel.canChooseDirectories = true 262 | panel.canCreateDirectories = true 263 | panel.allowsMultipleSelection = false 264 | 265 | panel.beginSheetModal( for: window ) 266 | { 267 | guard $0 == .OK, let url = panel.url 268 | else 269 | { 270 | return 271 | } 272 | 273 | self.convert( to: url, format: format ) 274 | } 275 | } 276 | 277 | private func convert( to url: URL, format: ImageFormat ) 278 | { 279 | let info = self.info 280 | self.loading = true 281 | self.info = "Converting Images - Please Wait..." 282 | self.progress = Progress( maxValue: self.images.count ) 283 | 284 | DispatchQueue.global( qos: .default ).async 285 | { 286 | let ext = switch format 287 | { 288 | case .tif: "tif" 289 | case .png: "png" 290 | case .jpg: "jpg" 291 | case .bmp: "bmp" 292 | case .gif: "gif" 293 | } 294 | 295 | let images: [ ( source: URL, destination: URL ) ] = self.images.map 296 | { 297 | let name = $0.deletingPathExtension().appendingPathExtension( ext ).lastPathComponent 298 | 299 | return ( $0, url.appending( component: name ) ) 300 | } 301 | 302 | let operations = images.map 303 | { 304 | image in 305 | 306 | let operation = BlockOperation 307 | { 308 | self.convertImage( url: image.source, to: image.destination, format: format ) 309 | 310 | DispatchQueue.main.async 311 | { 312 | guard let progress = self.progress 313 | else 314 | { 315 | return 316 | } 317 | 318 | progress.value += 1 319 | self.info = "Converted \( progress.value ) of \( images.count ) images..." 320 | } 321 | } 322 | 323 | operation.qualityOfService = .userInitiated 324 | 325 | return operation 326 | } 327 | 328 | self.queue.addOperations( operations, waitUntilFinished: true ) 329 | 330 | DispatchQueue.main.async 331 | { 332 | self.progress = nil 333 | self.loading = false 334 | self.info = info 335 | } 336 | } 337 | } 338 | 339 | private func convertImage( url: URL, to destination: URL, format: ImageFormat ) 340 | { 341 | do 342 | { 343 | try autoreleasepool 344 | { 345 | let data = try Data( contentsOf: url ) 346 | 347 | guard let image = NSImage( data: data )?.cgImage( forProposedRect: nil, context: nil, hints: [ : ] ) 348 | else 349 | { 350 | throw RuntimeError( message: "Cannot read image: \( url.lastPathComponent )" ) 351 | } 352 | 353 | let type = switch format 354 | { 355 | case .tif: UTType.tiff 356 | case .png: UTType.png 357 | case .jpg: UTType.jpeg 358 | case .bmp: UTType.bmp 359 | case .gif: UTType.gif 360 | } 361 | 362 | guard let writer = CGImageDestinationCreateWithURL( destination as CFURL, type.identifier as CFString, 1, nil ) 363 | else 364 | { 365 | throw RuntimeError( message: "Cannot prepare image for writing: \( url.lastPathComponent )" ) 366 | } 367 | 368 | CGImageDestinationAddImage( writer, image, nil ) 369 | 370 | if CGImageDestinationFinalize( writer ) == false 371 | { 372 | throw RuntimeError( message: "Cannot write image: \( destination.lastPathComponent )" ) 373 | } 374 | } 375 | } 376 | catch 377 | { 378 | self.showError( message: error.localizedDescription, closeWindow: false ) 379 | } 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /PhotoConverter/Classes/NSView.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Cocoa 26 | 27 | public extension NSView 28 | { 29 | func addFillingSubview( _ view: NSView ) 30 | { 31 | view.translatesAutoresizingMaskIntoConstraints = false 32 | view.frame = self.bounds 33 | 34 | self.addSubview( view ) 35 | self.addConstraint( NSLayoutConstraint( item: view, attribute: .width, relatedBy: .equal, toItem: self, attribute: .width, multiplier: 1, constant: 0 ) ) 36 | self.addConstraint( NSLayoutConstraint( item: view, attribute: .height, relatedBy: .equal, toItem: self, attribute: .height, multiplier: 1, constant: 0 ) ) 37 | self.addConstraint( NSLayoutConstraint( item: view, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0 ) ) 38 | self.addConstraint( NSLayoutConstraint( item: view, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0 ) ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /PhotoConverter/Classes/Progress.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Foundation 26 | 27 | @objc 28 | public class Progress: NSObject 29 | { 30 | @objc public dynamic var value: Int 31 | @objc public dynamic var maxValue: Int 32 | 33 | public init( maxValue: Int ) 34 | { 35 | self.value = 0 36 | self.maxValue = maxValue 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /PhotoConverter/Classes/RuntimeError.swift: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2023, Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the Software), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | import Foundation 26 | 27 | public class RuntimeError: LocalizedError, CustomStringConvertible 28 | { 29 | public private( set ) var message: String 30 | 31 | public init( message: String ) 32 | { 33 | self.message = message 34 | } 35 | 36 | public var errorDescription: String? 37 | { 38 | self.message 39 | } 40 | 41 | public var description: String 42 | { 43 | self.message 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /PhotoConverter/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.1.2 19 | CFBundleVersion 20 | 15 21 | LSApplicationCategoryType 22 | public.app-category.photography 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | Copyright © 2024 XS-Labs. All rights reserved. 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /PhotoConverter/PhotoConverter-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2021 Jean-David Gadina - www.xs-labs.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | ******************************************************************************/ 24 | 25 | -------------------------------------------------------------------------------- /PhotoConverter/PhotoConverter.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PhotoConverter 2 | ============== 3 | 4 | [![Build Status](https://img.shields.io/github/actions/workflow/status/macmade/PhotoConverter/ci-mac.yaml?label=macOS&logo=apple)](https://github.com/macmade/PhotoConverter/actions/workflows/ci-mac.yaml) 5 | [![Issues](http://img.shields.io/github/issues/macmade/PhotoConverter.svg?logo=github)](https://github.com/macmade/PhotoConverter/issues) 6 | ![Status](https://img.shields.io/badge/status-active-brightgreen.svg?logo=git) 7 | ![License](https://img.shields.io/badge/license-mit-brightgreen.svg?logo=open-source-initiative) 8 | [![Contact](https://img.shields.io/badge/follow-@macmade-blue.svg?logo=twitter&style=social)](https://twitter.com/macmade) 9 | [![Sponsor](https://img.shields.io/badge/sponsor-macmade-pink.svg?logo=github-sponsors&style=social)](https://github.com/sponsors/macmade) 10 | 11 | ### About 12 | 13 | A macOS application to convert images into various formats. 14 | 15 | ![Screenshot](Assets/Screenshot.png "Screenshot") 16 | 17 | License 18 | ------- 19 | 20 | Project is released under the terms of the MIT License. 21 | 22 | Repository Infos 23 | ---------------- 24 | 25 | Owner: Jean-David Gadina - XS-Labs 26 | Web: www.xs-labs.com 27 | Blog: www.noxeos.com 28 | Twitter: @macmade 29 | GitHub: github.com/macmade 30 | LinkedIn: ch.linkedin.com/in/macmade/ 31 | StackOverflow: stackoverflow.com/users/182676/macmade 32 | --------------------------------------------------------------------------------