diff --git a/Sources/ShipinKit/LumaAI/LumaAIError.swift b/Sources/ShipinKit/LumaAI/LumaAIError.swift new file mode 100644 index 0000000..f01cc53 --- /dev/null +++ b/Sources/ShipinKit/LumaAI/LumaAIError.swift @@ -0,0 +1,16 @@ +// +// LumaAIError.swift +// ShipinKit +// +// Created by Rudrank Riyam on 10/13/24. +// + + + +/// An error type representing errors from the Luma AI client. +public enum LumaAIError: Error { + /// An HTTP error with a status code. + case httpError(statusCode: Int) + /// A decoding error occurred. + case decodingError(underlying: Error) +} diff --git a/Sources/ShipinKit/LumaAI/LumaAIGenerationResponse.swift b/Sources/ShipinKit/LumaAI/LumaAIGenerationResponse.swift new file mode 100644 index 0000000..1a8e78e --- /dev/null +++ b/Sources/ShipinKit/LumaAI/LumaAIGenerationResponse.swift @@ -0,0 +1,58 @@ +// +// LumaAIGenerationResponse.swift +// ShipinKit +// +// Created by Rudrank Riyam on 10/13/24. +// + +import Foundation + +/// Represents the response from the Luma AI generation API. +public struct LumaAIGenerationResponse: Codable { + public let id: String + public let state: String + public let failureReason: String? + public let createdAt: String + public let assets: LumaAIAssets + public let version: String + public let request: LumaAIGenerationRequest + + enum CodingKeys: String, CodingKey { + case id + case state + case failureReason = "failure_reason" + case createdAt = "created_at" + case assets + case version + case request + } +} + +/// Contains the assets returned by the Luma AI generation API. +public struct LumaAIAssets: Codable { + public let video: String +} + +/// Represents the original request sent to the Luma AI generation API. +public struct LumaAIGenerationRequest: Codable { + public let prompt: String + public let aspectRatio: String + public let loop: Bool + public let keyframes: [String: LumaAIKeyframeData] + public let callbackURL: String? + + enum CodingKeys: String, CodingKey { + case prompt + case aspectRatio = "aspect_ratio" + case loop + case keyframes + case callbackURL = "callback_url" + } +} + +/// Represents keyframe data in the generation request. +public struct LumaAIKeyframeData: Codable { + public let type: String + public let url: String? + public let id: String? +} diff --git a/Sources/ShipinKit/LumaAIClient.swift b/Sources/ShipinKit/LumaAIClient.swift new file mode 100644 index 0000000..575279d --- /dev/null +++ b/Sources/ShipinKit/LumaAIClient.swift @@ -0,0 +1,66 @@ +// +// LumaAIClient.swift +// ShipinKit +// +// Created by Rudrank Riyam on 10/13/24. +// + +import Foundation + +/// A client for interacting with the Luma AI API. +public class LumaAIClient { + private let apiKey: String + private let session: URLSession + private let baseURL = URL(string: "https://api.lumalabs.ai")! + + /// Initializes a new instance of `LumaAIClient` + /// + /// - Parameters: + /// - apiKey: Your Luma AI API key. + /// - session: The URLSession to use for network requests. Defaults to `URLSession.shared`. + public init(apiKey: String, session: URLSession = .shared) { + self.apiKey = apiKey + self.session = session + } + + /// Initiates a generation request to the Luma AI API. + /// + /// - Parameters: + /// - prompt: The prompt for the generation. + /// - aspectRatio: The aspect ratio of the generated content. Defaults to "16:9". + /// - loop: Whether the generated content should loop. + /// - keyframes: A dictionary of keyframes. + /// - callbackURL: The callback URL to receive generation updates. + /// + /// - Returns: A `GenerationResponse` containing the result of the generation. + public func createGeneration(prompt: String, aspectRatio: String = "16:9", loop: Bool, keyframes: [String: LumaAIKeyframeData], callbackURL: String? = nil) async throws -> LumaAIGenerationResponse { + let url = baseURL.appendingPathComponent("/dream-machine/v1/generations") + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.timeoutInterval = 10 + request.addValue("application/json", forHTTPHeaderField: "accept") + request.addValue("application/json", forHTTPHeaderField: "content-type") + request.addValue("Bearer \(apiKey)", forHTTPHeaderField: "authorization") + + let requestBody = LumaAIGenerationRequest(prompt: prompt, aspectRatio: aspectRatio, loop: loop, keyframes: keyframes, callbackURL: callbackURL) + let encoder = JSONEncoder() + encoder.keyEncodingStrategy = .convertToSnakeCase + let bodyData = try encoder.encode(requestBody) + request.httpBody = bodyData + + let (data, response) = try await session.data(for: request) + + if let httpResponse = response as? HTTPURLResponse, !(200...299).contains(httpResponse.statusCode) { + throw LumaAIError.httpError(statusCode: httpResponse.statusCode) + } + + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + do { + let generationResponse = try decoder.decode(LumaAIGenerationResponse.self, from: data) + return generationResponse + } catch { + throw LumaAIError.decodingError(underlying: error) + } + } +}