MediaModel.swift 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. /*****************************************************************************
  2. * MediaModel.swift
  3. *
  4. * Copyright © 2018 VLC authors and VideoLAN
  5. * Copyright © 2018 Videolabs
  6. *
  7. * Authors: Soomin Lee <bubu@mikan.io>
  8. *
  9. * Refer to the COPYING file of the official project for license.
  10. *****************************************************************************/
  11. protocol MediaModel: MLBaseModel where MLType == VLCMLMedia { }
  12. extension MediaModel {
  13. func append(_ item: VLCMLMedia) {
  14. if !files.contains { $0 == item } {
  15. files.append(item)
  16. }
  17. }
  18. func delete(_ items: [VLCMLObject]) {
  19. do {
  20. for case let media as VLCMLMedia in items {
  21. if let mainFile = media.mainFile() {
  22. try FileManager.default.removeItem(atPath: mainFile.mrl.path)
  23. }
  24. }
  25. medialibrary.reload()
  26. }
  27. catch let error as NSError {
  28. assertionFailure("MediaModel: Delete failed: \(error.localizedDescription)")
  29. }
  30. }
  31. }
  32. // MARK: - Helpers
  33. extension MediaModel {
  34. /// Swap the given [VLCMLMedia] to the cached array.
  35. /// This only swaps media with the same VLCMLIdentifiers
  36. /// - Parameter medias: To be swapped medias
  37. /// - Returns: New array of `VLCMLMedia` if changes have been made, else return a unchanged cached version.
  38. func swapMedias(with medias: [VLCMLMedia]) -> [VLCMLMedia] {
  39. var newFiles = files
  40. // FIXME: This should be handled in a thread safe way
  41. for var media in medias {
  42. for (currentMediaIndex, file) in files.enumerated()
  43. where file.identifier() == media.identifier() {
  44. swap(&newFiles[currentMediaIndex], &media)
  45. break
  46. }
  47. }
  48. return newFiles
  49. }
  50. }
  51. extension VLCMLMedia {
  52. static func == (lhs: VLCMLMedia, rhs: VLCMLMedia) -> Bool {
  53. return lhs.identifier() == rhs.identifier()
  54. }
  55. }
  56. // MARK: - ViewModel
  57. extension VLCMLMedia {
  58. @objc func mediaDuration() -> String {
  59. return String(format: "%@", VLCTime(int: Int32(duration())))
  60. }
  61. @objc func formatSize() -> String {
  62. return ByteCountFormatter.string(fromByteCount: Int64(mainFile()?.size() ?? 0),
  63. countStyle: .file)
  64. }
  65. @objc func thumbnailImage() -> UIImage? {
  66. var image = UIImage(contentsOfFile: thumbnail.path)
  67. if image == nil {
  68. let isDarktheme = PresentationTheme.current == PresentationTheme.darkTheme
  69. if subtype() == .albumTrack {
  70. image = isDarktheme ? UIImage(named: "song-placeholder-dark") : UIImage(named: "song-placeholder-white")
  71. } else {
  72. image = isDarktheme ? UIImage(named: "movie-placeholder-dark") : UIImage(named: "movie-placeholder-white")
  73. }
  74. }
  75. return image
  76. }
  77. func accessibilityText(editing: Bool) -> String? {
  78. if editing {
  79. return title + " " + mediaDuration() + " " + formatSize()
  80. }
  81. return title + " " + albumTrackArtistName() + " " + (isNew ? NSLocalizedString("NEW", comment: "") : "")
  82. }
  83. }
  84. // MARK: - CoreSpotlight
  85. extension VLCMLMedia {
  86. func coreSpotlightAttributeSet() -> CSSearchableItemAttributeSet {
  87. let attributeSet = CSSearchableItemAttributeSet(itemContentType: "public.audiovisual-content")
  88. attributeSet.title = title
  89. attributeSet.metadataModificationDate = Date()
  90. attributeSet.addedDate = Date()
  91. attributeSet.duration = NSNumber(value: duration() / 1000)
  92. attributeSet.streamable = 0
  93. attributeSet.deliveryType = 0
  94. attributeSet.local = 1
  95. attributeSet.playCount = NSNumber(value: playCount())
  96. if isThumbnailGenerated() {
  97. attributeSet.thumbnailData = UIImage(contentsOfFile: thumbnail.path)?.jpegData(compressionQuality: 0.9)
  98. }
  99. attributeSet.codecs = codecs()
  100. attributeSet.languages = languages()
  101. if let audioTracks = audioTracks {
  102. for track in audioTracks {
  103. attributeSet.audioBitRate = NSNumber(value: track.bitrate())
  104. attributeSet.audioChannelCount = NSNumber(value: track.nbChannels())
  105. attributeSet.audioSampleRate = NSNumber(value: track.sampleRate())
  106. }
  107. }
  108. if let albumTrack = albumTrack {
  109. if let genre = albumTrack.genre {
  110. attributeSet.genre = genre.name
  111. }
  112. if let artist = albumTrack.artist {
  113. attributeSet.artist = artist.name
  114. }
  115. attributeSet.audioTrackNumber = NSNumber(value:albumTrack.trackNumber())
  116. if let album = albumTrack.album {
  117. attributeSet.artist = album.title
  118. }
  119. }
  120. return attributeSet
  121. }
  122. func codecs() -> [String] {
  123. var codecs = [String]()
  124. if let videoTracks = videoTracks {
  125. for track in videoTracks {
  126. codecs.append(track.codec)
  127. }
  128. }
  129. if let audioTracks = audioTracks {
  130. for track in audioTracks {
  131. codecs.append(track.codec)
  132. }
  133. }
  134. if let subtitleTracks = subtitleTracks {
  135. for track in subtitleTracks {
  136. codecs.append(track.codec)
  137. }
  138. }
  139. return codecs
  140. }
  141. func languages() -> [String] {
  142. var languages = [String]()
  143. if let videoTracks = videoTracks {
  144. for track in videoTracks where track.language != "" {
  145. languages.append(track.language)
  146. }
  147. }
  148. if let audioTracks = audioTracks {
  149. for track in audioTracks where track.language != "" {
  150. languages.append(track.language)
  151. }
  152. }
  153. if let subtitleTracks = subtitleTracks {
  154. for track in subtitleTracks where track.language != "" {
  155. languages.append(track.language)
  156. }
  157. }
  158. return languages
  159. }
  160. func updateCoreSpotlightEntry() {
  161. if !KeychainCoordinator.passcodeLockEnabled {
  162. let groupIdentifier = ProcessInfo.processInfo.environment["GROUP_IDENTIFIER"]
  163. let item = CSSearchableItem(uniqueIdentifier: "\(identifier())", domainIdentifier: groupIdentifier, attributeSet: coreSpotlightAttributeSet())
  164. CSSearchableIndex.default().indexSearchableItems([item], completionHandler: nil)
  165. }
  166. }
  167. }
  168. // MARK: - Search
  169. extension VLCMLMedia: SearchableMLModel {
  170. func contains(_ searchString: String) -> Bool {
  171. return title.lowercased().contains(searchString)
  172. }
  173. }
  174. extension VLCMLMedia {
  175. func albumTrackArtistName() -> String {
  176. guard let albumTrack = albumTrack else {
  177. return NSLocalizedString("UNKNOWN_ARTIST", comment: "")
  178. }
  179. return albumTrack.albumArtistName()
  180. }
  181. }