// // DirectMediaHelper.swift // Yattee // // Helper for creating Video/Stream objects from direct media URLs. // import Foundation /// Helper for handling direct media URLs (mp4, m3u8, etc.) without extraction. enum DirectMediaHelper { /// Provider name for direct media URLs. static let provider = "direct_media" // MARK: - Supported Extensions /// Video file extensions that can be played directly. static let videoExtensions: Set = [ "mp4", "m4v", "mov", "mkv", "avi", "webm", "wmv", "flv", "mpg", "mpeg", "3gp", "ts", "m2ts" ] /// Audio file extensions that can be played directly. static let audioExtensions: Set = [ "mp3", "m4a", "aac", "flac", "wav", "ogg", "opus" ] /// Streaming format extensions (HLS, DASH). static let streamingExtensions: Set = [ "m3u8", "mpd" ] /// All supported media extensions. static var allExtensions: Set { videoExtensions.union(audioExtensions).union(streamingExtensions) } // MARK: - Detection /// Checks if the URL points to a direct media file based on extension. static func isDirectMediaURL(_ url: URL) -> Bool { let pathExtension = url.pathExtension.lowercased() return allExtensions.contains(pathExtension) } /// Checks if the URL is a streaming format (HLS/DASH). static func isStreamingURL(_ url: URL) -> Bool { let pathExtension = url.pathExtension.lowercased() return streamingExtensions.contains(pathExtension) } /// Checks if the URL is an audio-only file. static func isAudioURL(_ url: URL) -> Bool { let pathExtension = url.pathExtension.lowercased() return audioExtensions.contains(pathExtension) } // MARK: - Video/Stream Creation /// Creates a Video model from a direct media URL. /// - Parameters: /// - url: The media URL. /// - probe: What `DirectMediaProbe` learned about the URL, when the path /// itself carries no filename (debrid/CDN links with an opaque ID). static func createVideo(from url: URL, probe: DirectMediaProbeResult? = nil) -> Video { let filename = probe?.filename ?? url.lastPathComponent let title = (filename as NSString).deletingPathExtension let host = url.host ?? "Direct Media" return Video( id: VideoID( source: .extracted(extractor: provider, originalURL: url), videoID: url.absoluteString ), title: title.isEmpty ? filename : title, description: nil, author: Author(id: provider, name: host, hasRealChannelInfo: false), duration: 0, // Unknown until playback publishedAt: nil, publishedText: nil, viewCount: nil, likeCount: nil, thumbnails: [], isLive: isStreamingURL(url) || isStreamingType(probe?.mimeType), // Treat HLS/DASH as potentially live isUpcoming: false, scheduledStartTime: nil ) } /// Creates a Stream from a direct media URL. /// - Parameters: /// - url: The media URL. /// - probe: What `DirectMediaProbe` learned about the URL; supplies the /// format, MIME type and size when the path has no extension. static func createStream(from url: URL, probe: DirectMediaProbeResult? = nil) -> Stream { let probedExtension = probe?.filename.map { ($0 as NSString).pathExtension.lowercased() } ?? "" let pathExtension = allExtensions.contains(url.pathExtension.lowercased()) ? url.pathExtension.lowercased() : (probedExtension.isEmpty ? url.pathExtension.lowercased() : probedExtension) let probedType = probe?.mimeType let isAudio = isAudioURL(url) || audioExtensions.contains(probedExtension) || (probedType?.hasPrefix("audio/") ?? false) let isStreaming = isStreamingURL(url) || streamingExtensions.contains(probedExtension) || isStreamingType(probedType) return Stream( url: url, resolution: isAudio ? nil : .p720, // Default assumption for video format: pathExtension, videoCodec: isAudio ? nil : "unknown", audioCodec: "unknown", bitrate: nil, fileSize: probe?.contentLength, isAudioOnly: isAudio, isLive: isStreaming, mimeType: mimeType(for: pathExtension) ?? probedType, audioLanguage: nil, audioTrackName: nil, isOriginalAudio: true, httpHeaders: nil, fps: nil ) } /// Whether a server-reported media type is an HLS or DASH manifest. static func isStreamingType(_ mimeType: String?) -> Bool { guard let mimeType else { return false } return [ "application/vnd.apple.mpegurl", "application/x-mpegurl", "audio/mpegurl", "audio/x-mpegurl", "application/dash+xml" ].contains(mimeType.lowercased()) } // MARK: - MIME Type Mapping /// Returns the MIME type for a file extension. static func mimeType(for extension: String) -> String? { switch `extension` { case "mp4", "m4v": return "video/mp4" case "mov": return "video/quicktime" case "mkv": return "video/x-matroska" case "avi": return "video/x-msvideo" case "webm": return "video/webm" case "wmv": return "video/x-ms-wmv" case "flv": return "video/x-flv" case "mpg", "mpeg": return "video/mpeg" case "3gp": return "video/3gpp" case "ts", "m2ts": return "video/mp2t" case "m3u8": return "application/vnd.apple.mpegurl" case "mpd": return "application/dash+xml" case "mp3": return "audio/mpeg" case "m4a": return "audio/mp4" case "aac": return "audio/aac" case "flac": return "audio/flac" case "wav": return "audio/wav" case "ogg": return "audio/ogg" case "opus": return "audio/opus" default: return nil } } }