├── AppleMusicBackup ├── Assets.xcassets │ ├── Contents.json │ └── AppIcon.appiconset │ │ └── Contents.json ├── DeveloperToken.swift ├── SongModel.swift ├── SongTableViewCell.swift ├── DictionaryExtension.swift ├── AppDelegate.swift ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── SceneDelegate.swift ├── SongTableViewController.swift └── AppleMusicAPI.swift ├── AppleMusicBackup.xcworkspace ├── xcuserdata │ └── Kray.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── AppleMusicBackup.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcuserdata │ │ └── Kray.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ └── Kray.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj ├── Podfile ├── Podfile.lock ├── musictoken.py ├── README.md └── .gitignore /AppleMusicBackup/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AppleMusicBackup.xcworkspace/xcuserdata/Kray.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krayc425/AppleMusicBackup/HEAD/AppleMusicBackup.xcworkspace/xcuserdata/Kray.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /AppleMusicBackup.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AppleMusicBackup.xcodeproj/project.xcworkspace/xcuserdata/Kray.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krayc425/AppleMusicBackup/HEAD/AppleMusicBackup.xcodeproj/project.xcworkspace/xcuserdata/Kray.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /AppleMusicBackup/DeveloperToken.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DeveloperToken.swift 3 | // AppleMusicBackup 4 | // 5 | // Created by Kuixi Song on 9/15/20. 6 | // Copyright © 2020 Kuixi Song. All rights reserved. 7 | // 8 | 9 | let developerToken = "YOUR_DEVELOPER_TOKEN" 10 | -------------------------------------------------------------------------------- /AppleMusicBackup.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AppleMusicBackup.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AppleMusicBackup.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment the next line to define a global platform for your project 2 | platform :ios, '13.0' 3 | 4 | target 'AppleMusicBackup' do 5 | # Comment the next line if you don't want to use dynamic frameworks 6 | use_frameworks! 7 | 8 | # Pods for AppleMusicBackup 9 | pod 'SwiftyJSON' 10 | pod 'Kingfisher' 11 | pod 'SVProgressHUD' 12 | 13 | end 14 | -------------------------------------------------------------------------------- /AppleMusicBackup.xcodeproj/xcuserdata/Kray.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | AppleMusicBackup.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 4 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /AppleMusicBackup/SongModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SongModel.swift 3 | // MusicPlayer 4 | // 5 | // Created by Sai Kambampati on 5/30/20. 6 | // Copyright © 2020 Sai Kambmapati. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct SongModel { 12 | 13 | var id: String 14 | var name: String 15 | var artistName: String 16 | var artworkURL: String 17 | 18 | init(id: String, name: String, artistName: String, artworkURL: String) { 19 | self.id = id 20 | self.name = name 21 | self.artworkURL = artworkURL 22 | self.artistName = artistName 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Kingfisher (5.14.1): 3 | - Kingfisher/Core (= 5.14.1) 4 | - Kingfisher/Core (5.14.1) 5 | - SVProgressHUD (2.2.5) 6 | - SwiftyJSON (5.0.0) 7 | 8 | DEPENDENCIES: 9 | - Kingfisher 10 | - SVProgressHUD 11 | - SwiftyJSON 12 | 13 | SPEC REPOS: 14 | trunk: 15 | - Kingfisher 16 | - SVProgressHUD 17 | - SwiftyJSON 18 | 19 | SPEC CHECKSUMS: 20 | Kingfisher: 8050bc6f7f68cbf3908bd04df7ccbac188f6d6d6 21 | SVProgressHUD: 1428aafac632c1f86f62aa4243ec12008d7a51d6 22 | SwiftyJSON: 36413e04c44ee145039d332b4f4e2d3e8d6c4db7 23 | 24 | PODFILE CHECKSUM: 7f54602c0ae10d83e263b8ab18c2d60fdac1f064 25 | 26 | COCOAPODS: 1.9.3 27 | -------------------------------------------------------------------------------- /musictoken.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import jwt 3 | 4 | 5 | secret = """-----BEGIN PRIVATE KEY----- 6 | YOUR_APPLE_MUSIC_P8_KEY 7 | -----END PRIVATE KEY-----""" 8 | keyId = "YOUR_MUSIC_KEY_ID" 9 | teamId = "YOUR_DEVELOPER_TEAM_ID" 10 | alg = 'ES256' 11 | 12 | time_now = datetime.datetime.now() 13 | time_expired = datetime.datetime.now() + datetime.timedelta(hours=4380) 14 | 15 | headers = { 16 | "alg": alg, 17 | "kid": keyId 18 | } 19 | 20 | payload = { 21 | "iss": teamId, 22 | "exp": int(time_expired.strftime("%s")), 23 | "iat": int(time_now.strftime("%s")) 24 | } 25 | 26 | 27 | if __name__ == "__main__": 28 | """Create an auth token""" 29 | token = jwt.encode(payload, secret, algorithm=alg, headers=headers) 30 | 31 | print("----TOKEN----") 32 | print(token) -------------------------------------------------------------------------------- /AppleMusicBackup/SongTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SongTableViewCell.swift 3 | // AppleMusicBackup 4 | // 5 | // Created by Kuixi Song on 9/16/20. 6 | // Copyright © 2020 Kuixi Song. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Kingfisher 11 | 12 | class SongTableViewCell: UITableViewCell { 13 | 14 | static let reuseId = "SongCellId" 15 | 16 | @IBOutlet weak var albumImageView: UIImageView? 17 | @IBOutlet weak var titleLabel: UILabel? 18 | @IBOutlet weak var artistLabel: UILabel? 19 | 20 | func config(_ song: SongModel) { 21 | titleLabel?.text = song.name 22 | artistLabel?.text = song.artistName 23 | if !song.artworkURL.isEmpty { 24 | let replacedString = song.artworkURL.replacingOccurrences(of: "{w}", with: "50").replacingOccurrences(of: "{h}", with: "50") 25 | if let url = URL(string: replacedString) { 26 | albumImageView?.kf.setImage(with: url) 27 | } 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /AppleMusicBackup/DictionaryExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DictionaryExtension.swift 3 | // AppleMusicBackup 4 | // 5 | // Created by Kuixi Song on 4/6/21. 6 | // Copyright © 2021 Kuixi Song. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Credit to https://stackoverflow.com/questions/26364914/http-request-in-swift-with-post-method 12 | 13 | extension Dictionary { 14 | func percentEncoded() -> Data? { 15 | return map { key, value in 16 | let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? "" 17 | let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? "" 18 | return escapedKey + "=" + escapedValue 19 | } 20 | .joined(separator: "&") 21 | .data(using: .utf8) 22 | } 23 | } 24 | 25 | extension CharacterSet { 26 | static let urlQueryValueAllowed: CharacterSet = { 27 | let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4 28 | let subDelimitersToEncode = "!$&'()*+,;=" 29 | 30 | var allowed = CharacterSet.urlQueryAllowed 31 | allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)") 32 | return allowed 33 | }() 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AppleMusicBackup 2 | 3 | This repo can help you backup your Apple Music library, and transfer them from one Apple ID to another. 4 | 5 | # Usage 6 | 7 | **You need to have Apple Music subscription to do following operations.** 8 | 9 | 1. Sign in you Apple ID, click **Get Songs** to fetch the list of your Apple Music library songs. 10 | 2. While in app, click **Save to File** to save a `.txt` file of a list of song ids somewhere (like iCloud Drive). 11 | 3. Sign in another Apple ID, click **Import from File** and select the file from last step to import these songs into your current library. 12 | 13 | # Steps 14 | 15 | 1. Follow steps [here](https://help.apple.com/developer-account/#/devce5522674) to generate a `MusicKit identifier` and a private `.p8` key file. 16 | 2. Install `pip` if you haven't. 17 | 3. Install these two packages. 18 | ```Sh 19 | sudo pip install pyjwt 20 | sudo pip install cryptography 21 | ``` 22 | 4. Copy your `Developer Team ID`, `MusicKit ID` and private key in the `.p8` file into `musictoken.py`. 23 | 5. Run `python(3) musictoken.py` 24 | 6. Copy the generated token to `AppleMusicBackup/DeveloperToken.swift`. 25 | 7. Remember to generate a new token after 24 hour expiration time. 26 | 8. Install `CocoaPods` if you haven't. 27 | 9. Run `pod install`. 28 | 10. Open `AppleMusicBackup.xcworkspace`, import your `.mobileprovision` file and run. -------------------------------------------------------------------------------- /AppleMusicBackup/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AppleMusicBackup 4 | // 5 | // Created by Kuixi Song on 9/15/20. 6 | // Copyright © 2020 Kuixi Song. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /AppleMusicBackup/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /AppleMusicBackup/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /AppleMusicBackup/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.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSAppleMusicUsageDescription 24 | Access to your Apple Music account is critical for the functionality of the app 25 | UIApplicationSceneManifest 26 | 27 | UIApplicationSupportsMultipleScenes 28 | 29 | UISceneConfigurations 30 | 31 | UIWindowSceneSessionRoleApplication 32 | 33 | 34 | UISceneConfigurationName 35 | Default Configuration 36 | UISceneDelegateClassName 37 | $(PRODUCT_MODULE_NAME).SceneDelegate 38 | UISceneStoryboardFile 39 | Main 40 | 41 | 42 | 43 | 44 | UILaunchStoryboardName 45 | LaunchScreen 46 | UIMainStoryboardFile 47 | Main 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UISupportedInterfaceOrientations~ipad 59 | 60 | UIInterfaceOrientationPortrait 61 | UIInterfaceOrientationPortraitUpsideDown 62 | UIInterfaceOrientationLandscapeLeft 63 | UIInterfaceOrientationLandscapeRight 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | 92 | musictoken.py 93 | AppleMusicBackup/DeveloperToken.swift 94 | -------------------------------------------------------------------------------- /AppleMusicBackup/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // AppleMusicBackup 4 | // 5 | // Created by Kuixi Song on 9/15/20. 6 | // Copyright © 2020 Kuixi Song. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 12 | 13 | var window: UIWindow? 14 | 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 19 | guard let _ = (scene as? UIWindowScene) else { return } 20 | (UIApplication.shared.delegate as! AppDelegate).window = window 21 | } 22 | 23 | func sceneDidDisconnect(_ scene: UIScene) { 24 | // Called as the scene is being released by the system. 25 | // This occurs shortly after the scene enters the background, or when its session is discarded. 26 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 27 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 28 | } 29 | 30 | func sceneDidBecomeActive(_ scene: UIScene) { 31 | // Called when the scene has moved from an inactive state to an active state. 32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 33 | } 34 | 35 | func sceneWillResignActive(_ scene: UIScene) { 36 | // Called when the scene will move from an active state to an inactive state. 37 | // This may occur due to temporary interruptions (ex. an incoming phone call). 38 | } 39 | 40 | func sceneWillEnterForeground(_ scene: UIScene) { 41 | // Called as the scene transitions from the background to the foreground. 42 | // Use this method to undo the changes made on entering the background. 43 | } 44 | 45 | func sceneDidEnterBackground(_ scene: UIScene) { 46 | // Called as the scene transitions from the foreground to the background. 47 | // Use this method to save data, release shared resources, and store enough scene-specific state information 48 | // to restore the scene back to its current state. 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /AppleMusicBackup/SongTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SongTableViewController.swift 3 | // AppleMusicBackup 4 | // 5 | // Created by Kuixi Song on 9/16/20. 6 | // Copyright © 2020 Kuixi Song. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import StoreKit 11 | import SwiftyJSON 12 | import SVProgressHUD 13 | 14 | class SongTableViewController: UITableViewController { 15 | 16 | var songs: [SongModel] = [] 17 | 18 | @IBAction func backupAction(_ sender: UIBarButtonItem) { 19 | SKCloudServiceController.requestAuthorization { [weak self] (status) in 20 | AppleMusicAPI().fetchAllSongs({ (models) in 21 | self?.songs = models 22 | DispatchQueue.main.async { 23 | SVProgressHUD.show(withStatus: "Done") 24 | SVProgressHUD.dismiss(withDelay: 2.0) 25 | self?.tableView.reloadData() 26 | } 27 | }) 28 | } 29 | } 30 | 31 | @IBAction func exportToJSONAction(_ sender: UIBarButtonItem) { 32 | let filePath = NSHomeDirectory() + "/Documents/all_song_ids.txt" 33 | var string = "" 34 | for song in songs { 35 | string.append(song.id) 36 | string.append("\n") 37 | } 38 | guard !string.isEmpty else { 39 | SVProgressHUD.show(withStatus: "No song") 40 | SVProgressHUD.dismiss(withDelay: 2.0) 41 | return 42 | } 43 | try! string.write(toFile: filePath, atomically: true, encoding: .utf8) 44 | let url = URL(fileURLWithPath: filePath) 45 | let activity = UIActivityViewController(activityItems: [url], applicationActivities: nil) 46 | present(activity, animated: true) 47 | } 48 | 49 | @IBAction func importFromJSONAction(_ sender: UIBarButtonItem) { 50 | let documentsPicker = UIDocumentPickerViewController(documentTypes: ["public.plain-text"], in: .open) 51 | documentsPicker.delegate = self 52 | documentsPicker.allowsMultipleSelection = false 53 | documentsPicker.modalPresentationStyle = .automatic 54 | present(documentsPicker, animated: true) 55 | } 56 | 57 | private func selectFile(_ url: URL) { 58 | let string = try! String(contentsOf: url) 59 | let ids = string.split(separator: "\n").map { String($0) } 60 | AppleMusicAPI().addSongs(ids) 61 | } 62 | 63 | // MARK: - Table view data source 64 | 65 | override func numberOfSections(in tableView: UITableView) -> Int { 66 | return 1 67 | } 68 | 69 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 70 | return songs.count 71 | } 72 | 73 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 74 | guard let cell = tableView.dequeueReusableCell(withIdentifier: SongTableViewCell.reuseId, for: indexPath) as? SongTableViewCell else { 75 | return UITableViewCell() 76 | } 77 | cell.config(songs[indexPath.row]) 78 | return cell 79 | } 80 | 81 | } 82 | 83 | extension SongTableViewController: UIDocumentPickerDelegate { 84 | 85 | func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentAt url: URL) { 86 | guard controller.documentPickerMode == .open, 87 | url.startAccessingSecurityScopedResource() else { 88 | SVProgressHUD.showError(withStatus: "No result") 89 | SVProgressHUD.dismiss(withDelay: 2.0) 90 | return 91 | } 92 | defer { 93 | DispatchQueue.main.async { 94 | url.stopAccessingSecurityScopedResource() 95 | } 96 | } 97 | controller.dismiss(animated: true) 98 | selectFile(url) 99 | } 100 | 101 | func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { 102 | controller.dismiss(animated: true) 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /AppleMusicBackup/AppleMusicAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppleMusicAPI.swift 3 | // MusicPlayer 4 | // 5 | // Created by Sai Kambampati on 5/30/20. 6 | // Copyright © 2020 Sai Kambmapati. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import StoreKit 11 | import SwiftyJSON 12 | import SVProgressHUD 13 | 14 | class AppleMusicAPI { 15 | 16 | private static let baseURL = "https://api.music.apple.com" 17 | 18 | func checkPermissions() { 19 | let status = SKCloudServiceController.authorizationStatus() 20 | switch status { 21 | case .notDetermined: 22 | SKCloudServiceController.requestAuthorization { [weak self] (status) in 23 | self?.checkPermissions() 24 | } 25 | case .authorized: 26 | print("Authorized") 27 | default: 28 | print("Denied") 29 | } 30 | } 31 | 32 | func addSongs(_ ids: [String]) { 33 | checkPermissions() 34 | var success = 0 35 | let total = ids.count 36 | DispatchQueue.global(qos: .userInitiated).async { 37 | SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken) { (receivedToken, error) in 38 | guard error == nil else { 39 | return 40 | } 41 | if let token = receivedToken { 42 | let iter = Int(ceil(Double(ids.count) / 100.0)) 43 | for i in 0.. ())) { 73 | checkPermissions() 74 | var resultSongs: [SongModel] = [] 75 | SVProgressHUD.show(withStatus: "Fetching") 76 | DispatchQueue.global(qos: .userInitiated).async { 77 | SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken) { (receivedToken, error) in 78 | guard error == nil else { 79 | return 80 | } 81 | if let userToken = receivedToken { 82 | var musicURL: String? = "/v1/me/library/songs" 83 | let semaphore = DispatchSemaphore(value: 0) 84 | while let url = musicURL, !url.isEmpty { 85 | print("Fetching \(url) with \(resultSongs.count) items") 86 | var musicRequest = URLRequest(url: URL(string: AppleMusicAPI.baseURL + url)!) 87 | musicRequest.httpMethod = "GET" 88 | musicRequest.addValue("Bearer \(developerToken)", forHTTPHeaderField: "Authorization") 89 | musicRequest.addValue(userToken, forHTTPHeaderField: "Music-User-Token") 90 | URLSession.shared.dataTask(with: musicRequest) { (data, response, error) in 91 | guard error == nil else { 92 | semaphore.signal() 93 | handler([]) 94 | return 95 | } 96 | if let data = data, 97 | let json = try? JSON(data: data), 98 | let results = (json["data"]).array { 99 | for result in results { 100 | let attributes = result.dictionaryValue["attributes"]?.dictionaryValue 101 | let artistName = attributes?["artistName"]?.stringValue 102 | let artworkURL = attributes?["artwork"]?.dictionaryValue["url"]?.stringValue 103 | guard let id = result.dictionaryValue["attributes"]?.dictionaryValue["playParams"]?.dictionaryValue["catalogId"]?.stringValue, 104 | let name = attributes?["name"]?.stringValue else { 105 | continue 106 | } 107 | resultSongs.append(SongModel(id: id, 108 | name: name, 109 | artistName: artistName ?? "", 110 | artworkURL: artworkURL ?? "")) 111 | } 112 | musicURL = json["next"].stringValue 113 | } 114 | semaphore.signal() 115 | }.resume() 116 | semaphore.wait() 117 | } 118 | handler(resultSongs) 119 | } 120 | } 121 | } 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /AppleMusicBackup/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 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 | 61 | 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 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /AppleMusicBackup.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 24013E192511DFA80009C25A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24013E182511DFA80009C25A /* AppDelegate.swift */; }; 11 | 24013E1B2511DFA80009C25A /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24013E1A2511DFA80009C25A /* SceneDelegate.swift */; }; 12 | 24013E202511DFA80009C25A /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 24013E1E2511DFA80009C25A /* Main.storyboard */; }; 13 | 24013E222511DFAB0009C25A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 24013E212511DFAB0009C25A /* Assets.xcassets */; }; 14 | 24013E252511DFAB0009C25A /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 24013E232511DFAB0009C25A /* LaunchScreen.storyboard */; }; 15 | 24013E2D2511E03E0009C25A /* AppleMusicAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24013E2C2511E03E0009C25A /* AppleMusicAPI.swift */; }; 16 | 24177F8A2511E08B00CED18A /* SongModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24177F892511E08B00CED18A /* SongModel.swift */; }; 17 | 24177F8C2511E40000CED18A /* DeveloperToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24177F8B2511E40000CED18A /* DeveloperToken.swift */; }; 18 | 24899F84261C4D2300C83B56 /* DictionaryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24899F83261C4D2300C83B56 /* DictionaryExtension.swift */; }; 19 | 249BB5EA25132C37005A0355 /* SongTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 249BB5E925132C37005A0355 /* SongTableViewCell.swift */; }; 20 | 249BB5EF25132DE1005A0355 /* SongTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 249BB5EE25132DE1005A0355 /* SongTableViewController.swift */; }; 21 | F9CB8691D2EE639F49A6F5B6 /* Pods_AppleMusicBackup.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F3020C8A5766081F8A7A488 /* Pods_AppleMusicBackup.framework */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | 24013E152511DFA80009C25A /* AppleMusicBackup.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppleMusicBackup.app; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | 24013E182511DFA80009C25A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 27 | 24013E1A2511DFA80009C25A /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 28 | 24013E1F2511DFA80009C25A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 29 | 24013E212511DFAB0009C25A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 30 | 24013E242511DFAB0009C25A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 31 | 24013E262511DFAB0009C25A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | 24013E2C2511E03E0009C25A /* AppleMusicAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleMusicAPI.swift; sourceTree = ""; }; 33 | 24177F892511E08B00CED18A /* SongModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SongModel.swift; sourceTree = ""; }; 34 | 24177F8B2511E40000CED18A /* DeveloperToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperToken.swift; sourceTree = ""; }; 35 | 24899F83261C4D2300C83B56 /* DictionaryExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DictionaryExtension.swift; sourceTree = ""; }; 36 | 249BB5E925132C37005A0355 /* SongTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongTableViewCell.swift; sourceTree = ""; }; 37 | 249BB5EE25132DE1005A0355 /* SongTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SongTableViewController.swift; sourceTree = ""; }; 38 | 3EF0261309E38CC31D8693E6 /* Pods-AppleMusicBackup.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppleMusicBackup.debug.xcconfig"; path = "Target Support Files/Pods-AppleMusicBackup/Pods-AppleMusicBackup.debug.xcconfig"; sourceTree = ""; }; 39 | 5A8939DDCA6093EDC2356938 /* Pods-AppleMusicBackup.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AppleMusicBackup.release.xcconfig"; path = "Target Support Files/Pods-AppleMusicBackup/Pods-AppleMusicBackup.release.xcconfig"; sourceTree = ""; }; 40 | 6F3020C8A5766081F8A7A488 /* Pods_AppleMusicBackup.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AppleMusicBackup.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | /* End PBXFileReference section */ 42 | 43 | /* Begin PBXFrameworksBuildPhase section */ 44 | 24013E122511DFA80009C25A /* Frameworks */ = { 45 | isa = PBXFrameworksBuildPhase; 46 | buildActionMask = 2147483647; 47 | files = ( 48 | F9CB8691D2EE639F49A6F5B6 /* Pods_AppleMusicBackup.framework in Frameworks */, 49 | ); 50 | runOnlyForDeploymentPostprocessing = 0; 51 | }; 52 | /* End PBXFrameworksBuildPhase section */ 53 | 54 | /* Begin PBXGroup section */ 55 | 24013E0C2511DFA80009C25A = { 56 | isa = PBXGroup; 57 | children = ( 58 | 24013E172511DFA80009C25A /* AppleMusicBackup */, 59 | 24013E162511DFA80009C25A /* Products */, 60 | B70D1DB3748864CF0753E1E8 /* Pods */, 61 | 95AECF5F2E58886C8CF66C9F /* Frameworks */, 62 | ); 63 | sourceTree = ""; 64 | }; 65 | 24013E162511DFA80009C25A /* Products */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | 24013E152511DFA80009C25A /* AppleMusicBackup.app */, 69 | ); 70 | name = Products; 71 | sourceTree = ""; 72 | }; 73 | 24013E172511DFA80009C25A /* AppleMusicBackup */ = { 74 | isa = PBXGroup; 75 | children = ( 76 | 24013E182511DFA80009C25A /* AppDelegate.swift */, 77 | 24013E1A2511DFA80009C25A /* SceneDelegate.swift */, 78 | 24013E2C2511E03E0009C25A /* AppleMusicAPI.swift */, 79 | 24177F8B2511E40000CED18A /* DeveloperToken.swift */, 80 | 249BB5EE25132DE1005A0355 /* SongTableViewController.swift */, 81 | 249BB5E925132C37005A0355 /* SongTableViewCell.swift */, 82 | 24177F892511E08B00CED18A /* SongModel.swift */, 83 | 24899F83261C4D2300C83B56 /* DictionaryExtension.swift */, 84 | 24013E1E2511DFA80009C25A /* Main.storyboard */, 85 | 24013E212511DFAB0009C25A /* Assets.xcassets */, 86 | 24013E232511DFAB0009C25A /* LaunchScreen.storyboard */, 87 | 24013E262511DFAB0009C25A /* Info.plist */, 88 | ); 89 | path = AppleMusicBackup; 90 | sourceTree = ""; 91 | }; 92 | 95AECF5F2E58886C8CF66C9F /* Frameworks */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 6F3020C8A5766081F8A7A488 /* Pods_AppleMusicBackup.framework */, 96 | ); 97 | name = Frameworks; 98 | sourceTree = ""; 99 | }; 100 | B70D1DB3748864CF0753E1E8 /* Pods */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 3EF0261309E38CC31D8693E6 /* Pods-AppleMusicBackup.debug.xcconfig */, 104 | 5A8939DDCA6093EDC2356938 /* Pods-AppleMusicBackup.release.xcconfig */, 105 | ); 106 | path = Pods; 107 | sourceTree = ""; 108 | }; 109 | /* End PBXGroup section */ 110 | 111 | /* Begin PBXNativeTarget section */ 112 | 24013E142511DFA80009C25A /* AppleMusicBackup */ = { 113 | isa = PBXNativeTarget; 114 | buildConfigurationList = 24013E292511DFAB0009C25A /* Build configuration list for PBXNativeTarget "AppleMusicBackup" */; 115 | buildPhases = ( 116 | C015D2EE28844C03CAB8049F /* [CP] Check Pods Manifest.lock */, 117 | 24013E112511DFA80009C25A /* Sources */, 118 | 24013E122511DFA80009C25A /* Frameworks */, 119 | 24013E132511DFA80009C25A /* Resources */, 120 | 45BBC7D6CD753A2EE16DF2AD /* [CP] Embed Pods Frameworks */, 121 | ); 122 | buildRules = ( 123 | ); 124 | dependencies = ( 125 | ); 126 | name = AppleMusicBackup; 127 | productName = AppleMusicBackup; 128 | productReference = 24013E152511DFA80009C25A /* AppleMusicBackup.app */; 129 | productType = "com.apple.product-type.application"; 130 | }; 131 | /* End PBXNativeTarget section */ 132 | 133 | /* Begin PBXProject section */ 134 | 24013E0D2511DFA80009C25A /* Project object */ = { 135 | isa = PBXProject; 136 | attributes = { 137 | LastSwiftUpdateCheck = 1170; 138 | LastUpgradeCheck = 1170; 139 | ORGANIZATIONNAME = "Kuixi Song"; 140 | TargetAttributes = { 141 | 24013E142511DFA80009C25A = { 142 | CreatedOnToolsVersion = 11.7; 143 | }; 144 | }; 145 | }; 146 | buildConfigurationList = 24013E102511DFA80009C25A /* Build configuration list for PBXProject "AppleMusicBackup" */; 147 | compatibilityVersion = "Xcode 9.3"; 148 | developmentRegion = en; 149 | hasScannedForEncodings = 0; 150 | knownRegions = ( 151 | en, 152 | Base, 153 | ); 154 | mainGroup = 24013E0C2511DFA80009C25A; 155 | productRefGroup = 24013E162511DFA80009C25A /* Products */; 156 | projectDirPath = ""; 157 | projectRoot = ""; 158 | targets = ( 159 | 24013E142511DFA80009C25A /* AppleMusicBackup */, 160 | ); 161 | }; 162 | /* End PBXProject section */ 163 | 164 | /* Begin PBXResourcesBuildPhase section */ 165 | 24013E132511DFA80009C25A /* Resources */ = { 166 | isa = PBXResourcesBuildPhase; 167 | buildActionMask = 2147483647; 168 | files = ( 169 | 24013E252511DFAB0009C25A /* LaunchScreen.storyboard in Resources */, 170 | 24013E222511DFAB0009C25A /* Assets.xcassets in Resources */, 171 | 24013E202511DFA80009C25A /* Main.storyboard in Resources */, 172 | ); 173 | runOnlyForDeploymentPostprocessing = 0; 174 | }; 175 | /* End PBXResourcesBuildPhase section */ 176 | 177 | /* Begin PBXShellScriptBuildPhase section */ 178 | 45BBC7D6CD753A2EE16DF2AD /* [CP] Embed Pods Frameworks */ = { 179 | isa = PBXShellScriptBuildPhase; 180 | buildActionMask = 2147483647; 181 | files = ( 182 | ); 183 | inputFileListPaths = ( 184 | "${PODS_ROOT}/Target Support Files/Pods-AppleMusicBackup/Pods-AppleMusicBackup-frameworks-${CONFIGURATION}-input-files.xcfilelist", 185 | ); 186 | name = "[CP] Embed Pods Frameworks"; 187 | outputFileListPaths = ( 188 | "${PODS_ROOT}/Target Support Files/Pods-AppleMusicBackup/Pods-AppleMusicBackup-frameworks-${CONFIGURATION}-output-files.xcfilelist", 189 | ); 190 | runOnlyForDeploymentPostprocessing = 0; 191 | shellPath = /bin/sh; 192 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AppleMusicBackup/Pods-AppleMusicBackup-frameworks.sh\"\n"; 193 | showEnvVarsInLog = 0; 194 | }; 195 | C015D2EE28844C03CAB8049F /* [CP] Check Pods Manifest.lock */ = { 196 | isa = PBXShellScriptBuildPhase; 197 | buildActionMask = 2147483647; 198 | files = ( 199 | ); 200 | inputFileListPaths = ( 201 | ); 202 | inputPaths = ( 203 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 204 | "${PODS_ROOT}/Manifest.lock", 205 | ); 206 | name = "[CP] Check Pods Manifest.lock"; 207 | outputFileListPaths = ( 208 | ); 209 | outputPaths = ( 210 | "$(DERIVED_FILE_DIR)/Pods-AppleMusicBackup-checkManifestLockResult.txt", 211 | ); 212 | runOnlyForDeploymentPostprocessing = 0; 213 | shellPath = /bin/sh; 214 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 215 | showEnvVarsInLog = 0; 216 | }; 217 | /* End PBXShellScriptBuildPhase section */ 218 | 219 | /* Begin PBXSourcesBuildPhase section */ 220 | 24013E112511DFA80009C25A /* Sources */ = { 221 | isa = PBXSourcesBuildPhase; 222 | buildActionMask = 2147483647; 223 | files = ( 224 | 24013E192511DFA80009C25A /* AppDelegate.swift in Sources */, 225 | 24177F8C2511E40000CED18A /* DeveloperToken.swift in Sources */, 226 | 249BB5EA25132C37005A0355 /* SongTableViewCell.swift in Sources */, 227 | 24013E1B2511DFA80009C25A /* SceneDelegate.swift in Sources */, 228 | 24013E2D2511E03E0009C25A /* AppleMusicAPI.swift in Sources */, 229 | 249BB5EF25132DE1005A0355 /* SongTableViewController.swift in Sources */, 230 | 24177F8A2511E08B00CED18A /* SongModel.swift in Sources */, 231 | 24899F84261C4D2300C83B56 /* DictionaryExtension.swift in Sources */, 232 | ); 233 | runOnlyForDeploymentPostprocessing = 0; 234 | }; 235 | /* End PBXSourcesBuildPhase section */ 236 | 237 | /* Begin PBXVariantGroup section */ 238 | 24013E1E2511DFA80009C25A /* Main.storyboard */ = { 239 | isa = PBXVariantGroup; 240 | children = ( 241 | 24013E1F2511DFA80009C25A /* Base */, 242 | ); 243 | name = Main.storyboard; 244 | sourceTree = ""; 245 | }; 246 | 24013E232511DFAB0009C25A /* LaunchScreen.storyboard */ = { 247 | isa = PBXVariantGroup; 248 | children = ( 249 | 24013E242511DFAB0009C25A /* Base */, 250 | ); 251 | name = LaunchScreen.storyboard; 252 | sourceTree = ""; 253 | }; 254 | /* End PBXVariantGroup section */ 255 | 256 | /* Begin XCBuildConfiguration section */ 257 | 24013E272511DFAB0009C25A /* Debug */ = { 258 | isa = XCBuildConfiguration; 259 | buildSettings = { 260 | ALWAYS_SEARCH_USER_PATHS = NO; 261 | CLANG_ANALYZER_NONNULL = YES; 262 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 263 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 264 | CLANG_CXX_LIBRARY = "libc++"; 265 | CLANG_ENABLE_MODULES = YES; 266 | CLANG_ENABLE_OBJC_ARC = YES; 267 | CLANG_ENABLE_OBJC_WEAK = YES; 268 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 269 | CLANG_WARN_BOOL_CONVERSION = YES; 270 | CLANG_WARN_COMMA = YES; 271 | CLANG_WARN_CONSTANT_CONVERSION = YES; 272 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 273 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 274 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 275 | CLANG_WARN_EMPTY_BODY = YES; 276 | CLANG_WARN_ENUM_CONVERSION = YES; 277 | CLANG_WARN_INFINITE_RECURSION = YES; 278 | CLANG_WARN_INT_CONVERSION = YES; 279 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 280 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 281 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 282 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 283 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 284 | CLANG_WARN_STRICT_PROTOTYPES = YES; 285 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 286 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 287 | CLANG_WARN_UNREACHABLE_CODE = YES; 288 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 289 | COPY_PHASE_STRIP = NO; 290 | DEBUG_INFORMATION_FORMAT = dwarf; 291 | ENABLE_STRICT_OBJC_MSGSEND = YES; 292 | ENABLE_TESTABILITY = YES; 293 | GCC_C_LANGUAGE_STANDARD = gnu11; 294 | GCC_DYNAMIC_NO_PIC = NO; 295 | GCC_NO_COMMON_BLOCKS = YES; 296 | GCC_OPTIMIZATION_LEVEL = 0; 297 | GCC_PREPROCESSOR_DEFINITIONS = ( 298 | "DEBUG=1", 299 | "$(inherited)", 300 | ); 301 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 302 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 303 | GCC_WARN_UNDECLARED_SELECTOR = YES; 304 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 305 | GCC_WARN_UNUSED_FUNCTION = YES; 306 | GCC_WARN_UNUSED_VARIABLE = YES; 307 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 308 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 309 | MTL_FAST_MATH = YES; 310 | ONLY_ACTIVE_ARCH = YES; 311 | SDKROOT = iphoneos; 312 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 313 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 314 | }; 315 | name = Debug; 316 | }; 317 | 24013E282511DFAB0009C25A /* Release */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | ALWAYS_SEARCH_USER_PATHS = NO; 321 | CLANG_ANALYZER_NONNULL = YES; 322 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 323 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 324 | CLANG_CXX_LIBRARY = "libc++"; 325 | CLANG_ENABLE_MODULES = YES; 326 | CLANG_ENABLE_OBJC_ARC = YES; 327 | CLANG_ENABLE_OBJC_WEAK = YES; 328 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 329 | CLANG_WARN_BOOL_CONVERSION = YES; 330 | CLANG_WARN_COMMA = YES; 331 | CLANG_WARN_CONSTANT_CONVERSION = YES; 332 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 333 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 334 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 335 | CLANG_WARN_EMPTY_BODY = YES; 336 | CLANG_WARN_ENUM_CONVERSION = YES; 337 | CLANG_WARN_INFINITE_RECURSION = YES; 338 | CLANG_WARN_INT_CONVERSION = YES; 339 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 340 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 341 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 343 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 344 | CLANG_WARN_STRICT_PROTOTYPES = YES; 345 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 346 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 347 | CLANG_WARN_UNREACHABLE_CODE = YES; 348 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 349 | COPY_PHASE_STRIP = NO; 350 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 351 | ENABLE_NS_ASSERTIONS = NO; 352 | ENABLE_STRICT_OBJC_MSGSEND = YES; 353 | GCC_C_LANGUAGE_STANDARD = gnu11; 354 | GCC_NO_COMMON_BLOCKS = YES; 355 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 356 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 357 | GCC_WARN_UNDECLARED_SELECTOR = YES; 358 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 359 | GCC_WARN_UNUSED_FUNCTION = YES; 360 | GCC_WARN_UNUSED_VARIABLE = YES; 361 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 362 | MTL_ENABLE_DEBUG_INFO = NO; 363 | MTL_FAST_MATH = YES; 364 | SDKROOT = iphoneos; 365 | SWIFT_COMPILATION_MODE = wholemodule; 366 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 367 | VALIDATE_PRODUCT = YES; 368 | }; 369 | name = Release; 370 | }; 371 | 24013E2A2511DFAB0009C25A /* Debug */ = { 372 | isa = XCBuildConfiguration; 373 | baseConfigurationReference = 3EF0261309E38CC31D8693E6 /* Pods-AppleMusicBackup.debug.xcconfig */; 374 | buildSettings = { 375 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 376 | CODE_SIGN_IDENTITY = "iPhone Developer"; 377 | CODE_SIGN_STYLE = Manual; 378 | DEVELOPMENT_TEAM = VEBQ28K9EA; 379 | INFOPLIST_FILE = AppleMusicBackup/Info.plist; 380 | LD_RUNPATH_SEARCH_PATHS = ( 381 | "$(inherited)", 382 | "@executable_path/Frameworks", 383 | ); 384 | PRODUCT_BUNDLE_IDENTIFIER = com.krayc.AMBackup; 385 | PRODUCT_NAME = "$(TARGET_NAME)"; 386 | PROVISIONING_PROFILE_SPECIFIER = AMBackup; 387 | SWIFT_VERSION = 5.0; 388 | TARGETED_DEVICE_FAMILY = "1,2"; 389 | }; 390 | name = Debug; 391 | }; 392 | 24013E2B2511DFAB0009C25A /* Release */ = { 393 | isa = XCBuildConfiguration; 394 | baseConfigurationReference = 5A8939DDCA6093EDC2356938 /* Pods-AppleMusicBackup.release.xcconfig */; 395 | buildSettings = { 396 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 397 | CODE_SIGN_IDENTITY = "iPhone Developer"; 398 | CODE_SIGN_STYLE = Manual; 399 | DEVELOPMENT_TEAM = VEBQ28K9EA; 400 | INFOPLIST_FILE = AppleMusicBackup/Info.plist; 401 | LD_RUNPATH_SEARCH_PATHS = ( 402 | "$(inherited)", 403 | "@executable_path/Frameworks", 404 | ); 405 | PRODUCT_BUNDLE_IDENTIFIER = com.krayc.AMBackup; 406 | PRODUCT_NAME = "$(TARGET_NAME)"; 407 | PROVISIONING_PROFILE_SPECIFIER = AMBackup; 408 | SWIFT_VERSION = 5.0; 409 | TARGETED_DEVICE_FAMILY = "1,2"; 410 | }; 411 | name = Release; 412 | }; 413 | /* End XCBuildConfiguration section */ 414 | 415 | /* Begin XCConfigurationList section */ 416 | 24013E102511DFA80009C25A /* Build configuration list for PBXProject "AppleMusicBackup" */ = { 417 | isa = XCConfigurationList; 418 | buildConfigurations = ( 419 | 24013E272511DFAB0009C25A /* Debug */, 420 | 24013E282511DFAB0009C25A /* Release */, 421 | ); 422 | defaultConfigurationIsVisible = 0; 423 | defaultConfigurationName = Release; 424 | }; 425 | 24013E292511DFAB0009C25A /* Build configuration list for PBXNativeTarget "AppleMusicBackup" */ = { 426 | isa = XCConfigurationList; 427 | buildConfigurations = ( 428 | 24013E2A2511DFAB0009C25A /* Debug */, 429 | 24013E2B2511DFAB0009C25A /* Release */, 430 | ); 431 | defaultConfigurationIsVisible = 0; 432 | defaultConfigurationName = Release; 433 | }; 434 | /* End XCConfigurationList section */ 435 | }; 436 | rootObject = 24013E0D2511DFA80009C25A /* Project object */; 437 | } 438 | --------------------------------------------------------------------------------