NCSubtitles.swift 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. //
  2. // NCSubtitles
  3. // Nextcloud
  4. //
  5. // Created by Marc Hervera.
  6. // Copyright 2017 Marc Hervera AVPlayerViewController-Subtitles v1.3.1 iOS
  7. //
  8. // Modified by Federico Malagoni on 23/02/22 for Nextcloud.
  9. //
  10. // Licensed under Apache License v2.0.
  11. //
  12. import AVKit
  13. class NCSubtitles {
  14. // MARK: - Private properties
  15. private var parsedPayload: NSDictionary?
  16. // MARK: - Public methods
  17. public init(file filePath: URL, encoding: String.Encoding = .utf8) throws {
  18. // Get string
  19. let string = try String(contentsOf: filePath, encoding: encoding)
  20. // Parse string
  21. parsedPayload = try NCSubtitles.parseSubRip(string)
  22. }
  23. public init(subtitles string: String) throws {
  24. // Parse string
  25. parsedPayload = try NCSubtitles.parseSubRip(string)
  26. }
  27. /// Search subtitles at time
  28. ///
  29. /// - Parameter time: Time
  30. /// - Returns: String if exists
  31. public func searchSubtitles(at time: TimeInterval) -> String? {
  32. return NCSubtitles.searchSubtitles(parsedPayload, time)
  33. }
  34. // MARK: - Static methods
  35. /// Subtitle parser
  36. ///
  37. /// - Parameter payload: Input string
  38. /// - Returns: NSDictionary
  39. static func parseSubRip(_ payload: String) throws -> NSDictionary? {
  40. // Prepare payload
  41. var payload = payload.replacingOccurrences(of: "\n\r\n", with: "\n\n")
  42. payload = payload.replacingOccurrences(of: "\n\n\n", with: "\n\n")
  43. payload = payload.replacingOccurrences(of: "\r\n", with: "\n")
  44. // Parsed dict
  45. let parsed = NSMutableDictionary()
  46. // Get groups
  47. let regexStr = "(\\d+)\\n([\\d:,.]+)\\s+-{2}\\>\\s+([\\d:,.]+)\\n([\\s\\S]*?(?=\\n{2,}|$))"
  48. let regex = try NSRegularExpression(pattern: regexStr, options: .caseInsensitive)
  49. let matches = regex.matches(in: payload, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSRange(location: 0, length: payload.count))
  50. for m in matches {
  51. let group = (payload as NSString).substring(with: m.range)
  52. // Get index
  53. var regex = try NSRegularExpression(pattern: "^[0-9]+", options: .caseInsensitive)
  54. var match = regex.matches(in: group, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSRange(location: 0, length: group.count))
  55. guard let i = match.first else {
  56. continue
  57. }
  58. let index = (group as NSString).substring(with: i.range)
  59. // Get "from" & "to" time
  60. regex = try NSRegularExpression(pattern: "\\d{1,2}:\\d{1,2}:\\d{1,2}[,.]\\d{1,3}", options: .caseInsensitive)
  61. match = regex.matches(in: group, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSRange(location: 0, length: group.count))
  62. guard match.count == 2 else {
  63. continue
  64. }
  65. guard let from = match.first, let to = match.last else {
  66. continue
  67. }
  68. var h: TimeInterval = 0.0, m: TimeInterval = 0.0, s: TimeInterval = 0.0, c: TimeInterval = 0.0
  69. let fromStr = (group as NSString).substring(with: from.range)
  70. var scanner = Scanner(string: fromStr)
  71. scanner.scanDouble(&h)
  72. scanner.scanString(":", into: nil)
  73. scanner.scanDouble(&m)
  74. scanner.scanString(":", into: nil)
  75. scanner.scanDouble(&s)
  76. scanner.scanString(",", into: nil)
  77. scanner.scanDouble(&c)
  78. let fromTime = (h * 3600.0) + (m * 60.0) + s + (c / 1000.0)
  79. let toStr = (group as NSString).substring(with: to.range)
  80. scanner = Scanner(string: toStr)
  81. scanner.scanDouble(&h)
  82. scanner.scanString(":", into: nil)
  83. scanner.scanDouble(&m)
  84. scanner.scanString(":", into: nil)
  85. scanner.scanDouble(&s)
  86. scanner.scanString(",", into: nil)
  87. scanner.scanDouble(&c)
  88. let toTime = (h * 3600.0) + (m * 60.0) + s + (c / 1000.0)
  89. // Get text & check if empty
  90. let range = NSRange(location: 0, length: to.range.location + to.range.length + 1)
  91. guard (group as NSString).length - range.length > 0 else {
  92. continue
  93. }
  94. let text = (group as NSString).replacingCharacters(in: range, with: "")
  95. // Create final object
  96. let final = NSMutableDictionary()
  97. final["from"] = fromTime
  98. final["to"] = toTime
  99. final["text"] = text
  100. parsed[index] = final
  101. }
  102. return parsed
  103. }
  104. /// Search subtitle on time
  105. ///
  106. /// - Parameters:
  107. /// - payload: Inout payload
  108. /// - time: Time
  109. /// - Returns: String
  110. static func searchSubtitles(_ payload: NSDictionary?, _ time: TimeInterval) -> String? {
  111. let predicate = NSPredicate(format: "(%f >= %K) AND (%f <= %K)", time, "from", time, "to")
  112. guard let values = payload?.allValues, let result = (values as NSArray).filtered(using: predicate).first as? NSDictionary else {
  113. return nil
  114. }
  115. guard let text = result.value(forKey: "text") as? String else {
  116. return nil
  117. }
  118. return text.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
  119. }
  120. }