Skip to content

Commit

Permalink
Update LumaAI and generation response
Browse files Browse the repository at this point in the history
  • Loading branch information
rudrankriyam committed Oct 14, 2024
1 parent 046942e commit fa5e553
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 30 deletions.
60 changes: 49 additions & 11 deletions Sources/ShipinKit/LumaAI/LumaAI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,13 @@ public actor LumaAI {
let bodyData = try encoder.encode(requestBody)
request.httpBody = bodyData

print("Request Body: \(String(data: bodyData, encoding: .utf8) ?? "")")

let (data, response) = try await URLSession.shared.data(for: request)

if let httpResponse = response as? HTTPURLResponse, !(200...299).contains(httpResponse.statusCode) {
throw LumaAIError.httpError(statusCode: httpResponse.statusCode)
print("Error Response: \(String(data: data, encoding: .utf8) ?? "")")
throw LumaAIError.httpError(statusCode: httpResponse.statusCode)
}

let decoder = JSONDecoder()
Expand Down Expand Up @@ -106,34 +109,55 @@ public actor LumaAI {
/// - Returns: An array of `LumaAIGenerationResponse` objects representing the list of generations.
///
/// - Throws: `LumaAIError.httpError` if the API request fails, or `LumaAIError.decodingError` if the response cannot be decoded.
public func listGenerations(limit: Int = 10, offset: Int = 0) async throws -> [LumaAIGenerationResponse] {
public func listGenerations(limit: Int = 10, offset: Int = 0) async throws -> LumaAIGenerationResponse {
debugPrint("Entering listGenerations with limit: \(limit), offset: \(offset)")

var components = URLComponents(url: baseURL.appendingPathComponent("/dream-machine/v1/generations"), resolvingAgainstBaseURL: true)
components?.queryItems = [
URLQueryItem(name: "limit", value: String(limit)),
URLQueryItem(name: "offset", value: String(offset))
]

guard let url = components?.url else {
debugPrint("Error: Failed to construct URL")
throw URLError(.badURL)
}
debugPrint("Constructed URL: \(url)")

var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "accept")
request.addValue("Bearer \(apiKey)", forHTTPHeaderField: "authorization")
debugPrint("Request headers: \(request.allHTTPHeaderFields ?? [:])")

debugPrint("Sending request...")
let (data, response) = try await URLSession.shared.data(for: request)
debugPrint("Received response")

if let httpResponse = response as? HTTPURLResponse, !(200...299).contains(httpResponse.statusCode) {
throw LumaAIError.httpError(statusCode: httpResponse.statusCode)
if let httpResponse = response as? HTTPURLResponse {
debugPrint("HTTP Status Code: \(httpResponse.statusCode)")
if !(200...299).contains(httpResponse.statusCode) {
debugPrint("Error: HTTP request failed")
throw LumaAIError.httpError(statusCode: httpResponse.statusCode)
}
}

debugPrint("Response data size: \(data.count) bytes")
if let responseString = String(data: data, encoding: .utf8) {
debugPrint("Response body: \(responseString)")
}

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
do {
let generationResponses = try decoder.decode([LumaAIGenerationResponse].self, from: data)
debugPrint("Attempting to decode response...")
let generationResponses = try decoder.decode(LumaAIGenerationResponse.self, from: data)
debugPrint("Successfully decoded response")
debugPrint("Number of generations: \(generationResponses.generations.count)")
return generationResponses
} catch {
debugPrint("Error: Failed to decode response")
debugPrint("Decoding error: \(error)")
throw LumaAIError.decodingError(underlying: error)
}
}
Expand All @@ -159,9 +183,23 @@ public actor LumaAI {

/// Retrieves a list of supported camera motions from the Luma AI API.
///
/// This method fetches an array of strings representing various camera motion options
/// that can be used in video generation. These motions define how the virtual camera
/// moves during the generated video sequence.
///
/// Possible camera motions include:
/// - Static: No camera movement
/// - Move Left/Right/Up/Down: Camera translates in the specified direction
/// - Push In/Pull Out: Camera moves forward or backward
/// - Zoom In/Out: Camera lens zooms in or out
/// - Pan Left/Right: Camera rotates horizontally
/// - Orbit Left/Right: Camera circles around the subject
/// - Crane Up/Down: Camera moves vertically, typically on a crane or jib
///
/// - Returns: An array of strings representing supported camera motions.
///
/// - Throws: `LumaAIError.httpError` if the API request fails, or `LumaAIError.decodingError` if the response cannot be decoded.
/// - Throws: `LumaAIError.httpError` if the API request fails, or
/// `LumaAIError.decodingError` if the response cannot be decoded.
public func listCameraMotions() async throws -> [String] {
let url = baseURL.appendingPathComponent("/dream-machine/v1/generations/camera_motion/list")
var request = URLRequest(url: url)
Expand Down Expand Up @@ -190,22 +228,22 @@ public actor LumaAI {
let task = Task<Void, Error> {
var currentResponse = initialResponse

while currentResponse.state != "completed" && currentResponse.state != "failed" {
while currentResponse.generations.first?.state != "completed" && currentResponse.generations.first?.state != "failed" {
try await Task.sleep(for: .seconds(5))
currentResponse = try await self.checkGenerationStatus(id: currentResponse.id)
currentResponse = try await self.checkGenerationStatus(id: currentResponse.generations.first?.id ?? "")
}
}

self.generationTasks[initialResponse.id] = task
self.generationTasks[initialResponse.generations.first?.id ?? ""] = task

do {
try await task.value
} catch {
self.generationTasks.removeValue(forKey: initialResponse.id)
self.generationTasks.removeValue(forKey: initialResponse.generations.first?.id ?? "")
throw error
}

self.generationTasks.removeValue(forKey: initialResponse.id)
self.generationTasks.removeValue(forKey: initialResponse.generations.first?.id ?? "")
}

private func checkGenerationStatus(id: String) async throws -> LumaAIGenerationResponse {
Expand Down
64 changes: 47 additions & 17 deletions Sources/ShipinKit/LumaAI/LumaAIGenerationResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,56 @@
//

import Foundation

/// Represents the response from the Luma AI generation API.
///
/// This struct encapsulates the data returned by the Luma AI generation API, including
/// information about the generation process, its current state, and associated assets.
public struct LumaAIGenerationResponse: Codable, Sendable {
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

/// A Boolean value indicating whether there are more results available.
public let hasMore: Bool?

/// The total count of generations in the response.
public let count: Int

/// The maximum number of generations that can be returned in a single response.
public let limit: Int

/// The number of generations skipped before the current set of results.
public let offset: Int

/// An array of generation details.
public let generations: [LumaAIGeneration]

/// Represents a single generation in the response.
public struct LumaAIGeneration: Codable, Sendable {
/// The unique identifier for the generation.
public let id: String

/// The current state of the generation (e.g., "completed", "failed", "in_progress").
public let state: String

/// The reason for failure, if the generation failed. Otherwise, it's null.
public let failureReason: String?

/// The timestamp when the generation was created.
public let createdAt: String

/// The assets associated with the generation.
public let assets: LumaAIAssets

/// The version of the Luma AI API used for this generation.
public let version: String

/// The original request parameters used for this generation.
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
case hasMore = "has_more"
case count
case limit
case offset
case generations
}
}

Expand Down Expand Up @@ -54,7 +85,6 @@ public struct LumaAIGenerationRequest: Codable, Sendable {
public struct LumaAIKeyframeData: Codable, Sendable {
public let type: LumaAIKeyframeType
public let url: String?
public let id: String?
}

/// Represents the type of keyframe in the generation request.
Expand Down
4 changes: 2 additions & 2 deletions Tests/ShipinKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import XCTest
@testable import ShipinKit

final class ShipinKitTests: XCTestCase {
var shipinKit: ShipinKit!
var shipinKit: RunwayML!

override func setUp() {
super.setUp()
shipinKit = ShipinKit(apiKey: "KEY_HERE")
shipinKit = RunwayML(apiKey: "KEY_HERE")
}

func testShipinKitInitialization() {
Expand Down

0 comments on commit fa5e553

Please sign in to comment.