Class: Lipdub::Resources::Shots
- Defined in:
- lib/lipdub/resources/shots.rb
Instance Attribute Summary
Attributes inherited from Base
Instance Method Summary collapse
-
#actors(shot_id) ⇒ Hash
Get actors for a shot.
-
#add_frame_buffer(ranges, buffer_frames: 10, fps: 30, video_duration: nil) ⇒ Array<Array>
Adds frame buffer to timecode ranges for seamless blending.
-
#download(shot_id, generate_id) ⇒ Hash
Download generated video.
-
#download_file(shot_id, generate_id, file_path) ⇒ String
Download generated video file directly to a local path.
-
#generate(shot_id:, audio_id:, output_filename:, language: nil, start_frame: nil, loop_video: nil, full_resolution: nil, callback_url: nil, timecode_ranges: nil) ⇒ Hash
Generate lip-dubbed video.
-
#generate_and_wait(shot_id:, audio_id:, output_filename:, language: nil, start_frame: nil, loop_video: nil, full_resolution: nil, callback_url: nil, timecode_ranges: nil, polling_interval: 10, max_wait_time: 1800) ⇒ Hash
Complete generation workflow: generate and wait for completion.
-
#generate_multi_actor(shot_id:, **params) ⇒ Hash
Generate multi-actor LipDub for a shot.
-
#generation_status(shot_id, generate_id) ⇒ Hash
Get generation status.
-
#list(page: 1, per_page: 20) ⇒ Hash
List all available shots.
-
#parse_timecode_to_seconds(timecode, fps: 30) ⇒ Float
Converts timecode to seconds.
-
#status(shot_id) ⇒ Hash
Get shot processing status.
-
#translate(shot_id:, source_language:, target_language:, full_resolution: nil) ⇒ Hash
Translate a LipDub for a shot.
-
#validate_timecode_ranges(ranges, video_duration: nil) ⇒ Boolean
Validates timecode ranges for selective lip-dubbing.
Methods inherited from Base
Constructor Details
This class inherits a constructor from Lipdub::Resources::Base
Instance Method Details
#actors(shot_id) ⇒ Hash
Get actors for a shot
235 236 237 |
# File 'lib/lipdub/resources/shots.rb', line 235 def actors(shot_id) get("/v1/shots/#{shot_id}/actors") end |
#add_frame_buffer(ranges, buffer_frames: 10, fps: 30, video_duration: nil) ⇒ Array<Array>
Adds frame buffer to timecode ranges for seamless blending
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 |
# File 'lib/lipdub/resources/shots.rb', line 330 def add_frame_buffer(ranges, buffer_frames: 10, fps: 30, video_duration: nil) return ranges if ranges.nil? || ranges.empty? buffer_seconds = buffer_frames.to_f / fps ranges.map do |start_time, end_time| start_seconds = parse_timecode_to_seconds(start_time, fps: fps) end_seconds = parse_timecode_to_seconds(end_time, fps: fps) buffered_start = [start_seconds - buffer_seconds, 0.0].max buffered_end = end_seconds + buffer_seconds if video_duration buffered_end = [buffered_end, video_duration].min end [buffered_start, buffered_end] end end |
#download(shot_id, generate_id) ⇒ Hash
Download generated video
119 120 121 |
# File 'lib/lipdub/resources/shots.rb', line 119 def download(shot_id, generate_id) get("/v1/shots/#{shot_id}/generate/#{generate_id}/download") end |
#download_file(shot_id, generate_id, file_path) ⇒ String
Download generated video file directly to a local path
133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/lipdub/resources/shots.rb', line 133 def download_file(shot_id, generate_id, file_path) download_response = download(shot_id, generate_id) download_url = download_response.dig("data", "download_url") if download_response.is_a?(Hash) # Handle case where response might still be a string (fallback) if download_response.is_a?(String) parsed = JSON.parse(download_response) download_url = parsed.dig("data", "download_url") end raise APIError, "Download URL not found in response" unless download_url download_file_from_url(download_url, file_path) end |
#generate(shot_id:, audio_id:, output_filename:, language: nil, start_frame: nil, loop_video: nil, full_resolution: nil, callback_url: nil, timecode_ranges: nil) ⇒ Hash
Generate lip-dubbed video
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/lipdub/resources/shots.rb', line 77 def generate(shot_id:, audio_id:, output_filename:, language: nil, start_frame: nil, loop_video: nil, full_resolution: nil, callback_url: nil, timecode_ranges: nil) body = { audio_id: audio_id, output_filename: output_filename } body[:language] = language if language body[:start_frame] = start_frame if start_frame body[:loop_video] = loop_video unless loop_video.nil? body[:full_resolution] = full_resolution unless full_resolution.nil? body[:callback_url] = callback_url if callback_url body[:timecode_ranges] = timecode_ranges if timecode_ranges post("/v1/shots/#{shot_id}/generate", body) end |
#generate_and_wait(shot_id:, audio_id:, output_filename:, language: nil, start_frame: nil, loop_video: nil, full_resolution: nil, callback_url: nil, timecode_ranges: nil, polling_interval: 10, max_wait_time: 1800) ⇒ Hash
Complete generation workflow: generate and wait for completion
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 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 |
# File 'lib/lipdub/resources/shots.rb', line 171 def generate_and_wait(shot_id:, audio_id:, output_filename:, language: nil, start_frame: nil, loop_video: nil, full_resolution: nil, callback_url: nil, timecode_ranges: nil, polling_interval: 10, max_wait_time: 1800) # Start generation generate_response = generate( shot_id: shot_id, audio_id: audio_id, output_filename: output_filename, language: language, start_frame: start_frame, loop_video: loop_video, full_resolution: full_resolution, callback_url: callback_url, timecode_ranges: timecode_ranges ) generate_id = nil if generate_response.is_a?(Hash) generate_id = generate_response["generate_id"] || generate_response.dig("data", "generate_id") elsif generate_response.is_a?(String) parsed = JSON.parse(generate_response) generate_id = parsed["generate_id"] || parsed.dig("data", "generate_id") end raise APIError, "Generate ID not found in response" unless generate_id # Poll for completion start_time = Time.now loop do status_response = generation_status(shot_id, generate_id) # Handle both Hash and String responses status = nil if status_response.is_a?(Hash) status = status_response.dig("data", "status") || status_response["status"] elsif status_response.is_a?(String) parsed = JSON.parse(status_response) status = parsed.dig("data", "status") || parsed["status"] status_response = parsed # Use parsed version for return end case status when "completed", "success" return status_response when "failed", "error" raise APIError, "Generation failed: #{status_response}" end # Check timeout if Time.now - start_time > max_wait_time raise TimeoutError, "Generation did not complete within #{max_wait_time} seconds" end sleep(polling_interval) end end |
#generate_multi_actor(shot_id:, **params) ⇒ Hash
Generate multi-actor LipDub for a shot
275 276 277 |
# File 'lib/lipdub/resources/shots.rb', line 275 def generate_multi_actor(shot_id:, **params) post("/v1/shots/#{shot_id}/generate-multi-actor", params) end |
#generation_status(shot_id, generate_id) ⇒ Hash
Get generation status
102 103 104 |
# File 'lib/lipdub/resources/shots.rb', line 102 def generation_status(shot_id, generate_id) get("/v1/shots/#{shot_id}/generate/#{generate_id}") end |
#list(page: 1, per_page: 20) ⇒ Hash
List all available shots
29 30 31 32 33 34 35 36 37 |
# File 'lib/lipdub/resources/shots.rb', line 29 def list(page: 1, per_page: 20) validate_pagination_params!(page, per_page) params = { page: page, per_page: per_page } get("/v1/shots", params) end |
#parse_timecode_to_seconds(timecode, fps: 30) ⇒ Float
Converts timecode to seconds
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/lipdub/resources/shots.rb', line 354 def parse_timecode_to_seconds(timecode, fps: 30) case timecode when Numeric timecode.to_f when String if timecode.match?(/^\d{2}:\d{2}:\d{2}:\d{2}$/) # SMPTE format: HH:MM:SS:FF hours, minutes, seconds, frames = timecode.split(':').map(&:to_i) hours * 3600 + minutes * 60 + seconds + frames.to_f / fps else # Try parsing as float string timecode.to_f end else raise ArgumentError, "Invalid timecode format: #{timecode}. Use numeric seconds or SMPTE format (HH:MM:SS:FF)" end end |
#status(shot_id) ⇒ Hash
Get shot processing status
46 47 48 |
# File 'lib/lipdub/resources/shots.rb', line 46 def status(shot_id) get("/v1/shots/#{shot_id}/status") end |
#translate(shot_id:, source_language:, target_language:, full_resolution: nil) ⇒ Hash
Translate a LipDub for a shot
254 255 256 257 258 259 260 261 262 |
# File 'lib/lipdub/resources/shots.rb', line 254 def translate(shot_id:, source_language:, target_language:, full_resolution: nil) body = { source_language: source_language, target_language: target_language } body[:full_resolution] = full_resolution unless full_resolution.nil? post("/v1/shots/#{shot_id}/translate", body) end |
#validate_timecode_ranges(ranges, video_duration: nil) ⇒ Boolean
Validates timecode ranges for selective lip-dubbing
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 |
# File 'lib/lipdub/resources/shots.rb', line 286 def validate_timecode_ranges(ranges, video_duration: nil) return true if ranges.nil? || ranges.empty? unless ranges.is_a?(Array) raise ArgumentError, "timecode_ranges must be an array" end ranges.each_with_index do |range, index| unless range.is_a?(Array) && range.length == 2 raise ArgumentError, "Each timecode range must be an array of [start, end] at index #{index}" end start_time, end_time = range start_seconds = parse_timecode_to_seconds(start_time) end_seconds = parse_timecode_to_seconds(end_time) if start_seconds >= end_seconds raise ArgumentError, "Start time must be before end time in range #{index}: #{range}" end if video_duration && end_seconds > video_duration raise ArgumentError, "End time #{end_time} exceeds video duration #{video_duration} in range #{index}" end end # Check for overlapping ranges sorted_ranges = ranges.map { |r| [parse_timecode_to_seconds(r[0]), parse_timecode_to_seconds(r[1])] } .sort_by(&:first) sorted_ranges.each_cons(2) do |(prev_start, prev_end), (curr_start, curr_end)| if curr_start < prev_end raise ArgumentError, "Overlapping timecode ranges detected: [#{prev_start}, #{prev_end}] and [#{curr_start}, #{curr_end}]" end end true end |