Skip to content

Commit

Permalink
Final Relese 0.1.1
Browse files Browse the repository at this point in the history
  • Loading branch information
swagat-cdi committed Mar 14, 2021
1 parent 79ec7cc commit 6698095
Show file tree
Hide file tree
Showing 2 changed files with 353 additions and 0 deletions.
Empty file.
353 changes: 353 additions & 0 deletions SNAVPlayerSubtitles/Classes/SNAVPlayerSubtitles.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,353 @@
//
// SNAVPlayerSubtitles.swift
// Pods-SNAVPlayerSubtitles_Example
//
// Created by SWAGAT-CDI on 14/03/21.
//
import ObjectiveC
import MediaPlayer
import AVKit
import CoreMedia

private struct AssociatedKeys {
static var FontKey = "FontKey"
static var ColorKey = "FontKey"
static var SubtitleKey = "SubtitleKey"
static var SubtitleHeightKey = "SubtitleHeightKey"
static var PayloadKey = "PayloadKey"
}

@objc public class Subtitles : NSObject {

// MARK: - Properties
fileprivate var parsedPayload: NSDictionary?

// MARK: - Public methods
public init(file filePath: URL, encoding: String.Encoding = String.Encoding.utf8) {

// Get string
let string = try! String(contentsOf: filePath, encoding: encoding)

// Parse string
parsedPayload = Subtitles.parseSubRip(string)

}

@objc public init(subtitles string: String) {

// Parse string
parsedPayload = Subtitles.parseSubRip(string)

}

/// Search subtitles at time
///
/// - Parameter time: Time
/// - Returns: String if exists
@objc public func searchSubtitles(at time: TimeInterval) -> String? {

return Subtitles.searchSubtitles(parsedPayload, time)

}

// MARK: - Private methods

/// Subtitle parser
///
/// - Parameter payload: Input string
/// - Returns: NSDictionary
fileprivate static func parseSubRip(_ payload: String) -> NSDictionary? {

do {

// Prepare payload
// var payload = payload.replacingOccurrences(of: "\n\r\n", with: "\n\n")
// payload = payload.replacingOccurrences(of: "\n\n\n", with: "\n\n")
// payload = payload.replacingOccurrences(of: "\r\n", with: "\n")

// Parsed dict
let parsed = NSMutableDictionary()

// Get groups
var index: Int = 0
let regexStr = "(?m)^(\\d{2}:\\d{2}:\\d{2}\\.\\d+) +--> +(\\d{2}:\\d{2}:\\d{2}\\.\\d+).*[\r\n]+\\s*(?s)((?:(?!\r?\n\r?\n).)*)"
let regex = try NSRegularExpression(pattern: regexStr, options: .caseInsensitive)
let matches = regex.matches(in: payload, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, payload.count))
for m in matches {

let group = (payload as NSString).substring(with: m.range)

// Get index
var regex = try NSRegularExpression(pattern: "^[0-9]+", options: .caseInsensitive)
var match = regex.matches(in: group, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, group.count))
guard match.first != nil else {
continue
}
// let index = (group as NSString).substring(with: i.range)

// Get "from" & "to" time
regex = try NSRegularExpression(pattern: "\\d{1,2}:\\d{1,2}:\\d{1,2}[\\..]\\d{1,3}", options: .caseInsensitive)
match = regex.matches(in: group, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, group.count))
guard match.count == 2 else {
continue
}
guard let from = match.first, let to = match.last else {
continue
}

var h: TimeInterval = 0.0, m: TimeInterval = 0.0, s: TimeInterval = 0.0, c: TimeInterval = 0.0



var fromTime: Double
var toTime: Double

if #available(iOS 13.0, *) {

let fromStr = (group as NSString).substring(with: from.range)
var scanner = Scanner(string: fromStr)
h = scanner.scanDouble() ?? 0.0
let _ = scanner.scanString(":")
m = scanner.scanDouble() ?? 0.0
let _ = scanner.scanString(":")
s = scanner.scanDouble() ?? 0.0
let _ = scanner.scanString(".")
c = scanner.scanDouble() ?? 0.0
fromTime = (h * 3600.0) + (m * 60.0) + s + (c / 1000.0)

let toStr = (group as NSString).substring(with: to.range)
scanner = Scanner(string: toStr)
h = scanner.scanDouble() ?? 0.0
let _ = scanner.scanString(":")
m = scanner.scanDouble() ?? 0.0
let _ = scanner.scanString(":")
s = scanner.scanDouble() ?? 0.0
let _ = scanner.scanString(".")
c = scanner.scanDouble() ?? 0.0
toTime = (h * 3600.0) + (m * 60.0) + s + (c / 1000.0)

}else{

let fromStr = (group as NSString).substring(with: from.range)
var scanner = Scanner(string: fromStr)
scanner.scanDouble(&h)
scanner.scanString(":", into: nil)
scanner.scanDouble(&m)
scanner.scanString(":",into: nil)
scanner.scanDouble(&s)
scanner.scanString(".", into: nil)
scanner.scanDouble(&c)
fromTime = (h * 3600.0) + (m * 60.0) + s + (c / 1000.0)

let toStr = (group as NSString).substring(with: to.range)
scanner = Scanner(string: toStr)
scanner.scanDouble(&h)
scanner.scanString(":", into: nil)
scanner.scanDouble(&m)
scanner.scanString(":",into: nil)
scanner.scanDouble(&s)
scanner.scanString(".", into: nil)
scanner.scanDouble(&c)
toTime = (h * 3600.0) + (m * 60.0) + s + (c / 1000.0)

}




// Get text & check if empty
let range = NSMakeRange(0, to.range.location + to.range.length + 1)
guard (group as NSString).length - range.length > 0 else {
continue
}
let text = (group as NSString).replacingCharacters(in: range, with: "")

// Create final object
let final = NSMutableDictionary()
final["from"] = fromTime
final["to"] = toTime
final["text"] = text
parsed[index] = final

index = index+1

}

return parsed

} catch {

return nil

}

}

/// Search subtitle on time
///
/// - Parameters:
/// - payload: Inout payload
/// - time: Time
/// - Returns: String
fileprivate static func searchSubtitles(_ payload: NSDictionary?, _ time: TimeInterval) -> String? {

let predicate = NSPredicate(format: "(%f >= %K) AND (%f <= %K)", time, "from", time, "to")

guard let values = payload?.allValues, let result = (values as NSArray).filtered(using: predicate).first as? NSDictionary else {
return nil
}

guard let text = result.value(forKey: "text") as? String else {
return nil
}

return text.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)

}

}

public extension AVPlayerViewController {

// MARK: - Public properties
var subtitleLabel: UILabel? {
get { return objc_getAssociatedObject(self, &AssociatedKeys.SubtitleKey) as? UILabel }
set (value) { objc_setAssociatedObject(self, &AssociatedKeys.SubtitleKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}

// MARK: - Private properties
fileprivate var subtitleLabelHeightConstraint: NSLayoutConstraint? {
get { return objc_getAssociatedObject(self, &AssociatedKeys.SubtitleHeightKey) as? NSLayoutConstraint }
set (value) { objc_setAssociatedObject(self, &AssociatedKeys.SubtitleHeightKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}
fileprivate var parsedPayload: NSDictionary? {
get { return objc_getAssociatedObject(self, &AssociatedKeys.PayloadKey) as? NSDictionary }
set (value) { objc_setAssociatedObject(self, &AssociatedKeys.PayloadKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}

// MARK: - Public methods
func addSubtitles() -> Self {

// Create label
addSubtitleLabel()

return self

}

func open(fileFromLocal filePath: URL, encoding: String.Encoding = String.Encoding.utf8) {

let contents = try! String(contentsOf: filePath, encoding: encoding)
show(subtitles: contents)
}

func open(fileFromRemote filePath: URL, encoding: String.Encoding = String.Encoding.utf8) {


subtitleLabel?.text = "..."
URLSession.shared.dataTask(with: filePath, completionHandler: { (data, response, error) -> Void in

if let httpResponse = response as? HTTPURLResponse {
let statusCode = httpResponse.statusCode

//Check status code
if statusCode != 200 {
NSLog("Subtitle Error: \(httpResponse.statusCode) - \(error?.localizedDescription ?? "")")
return
}
}
// Update UI elements on main thread
DispatchQueue.main.async(execute: {
self.subtitleLabel?.text = ""

if let checkData = data as Data? {
if let contents = String(data: checkData, encoding: encoding) {
self.show(subtitles: contents)
}
}

})
}).resume()
}



func show(subtitles string: String) {
// Parse
parsedPayload = Subtitles.parseSubRip(string)
addPeriodicNotification(parsedPayload: parsedPayload!)

}

func showByDictionary(dictionaryContent: NSMutableDictionary) {

// Add Dictionary content direct to Payload
parsedPayload = dictionaryContent
addPeriodicNotification(parsedPayload: parsedPayload!)

}

func addPeriodicNotification(parsedPayload: NSDictionary) {
// Add periodic notifications
self.player?.addPeriodicTimeObserver(
forInterval: CMTimeMake(value: 1, timescale: 60),
queue: DispatchQueue.main,
using: { [weak self] (time) -> Void in

guard let strongSelf = self else { return }
guard let label = strongSelf.subtitleLabel else { return }

// Search && show subtitles
label.text = Subtitles.searchSubtitles(strongSelf.parsedPayload, time.seconds)

// Adjust size
let baseSize = CGSize(width: label.bounds.width, height: CGFloat.greatestFiniteMagnitude)
let rect = label.sizeThatFits(baseSize)
if label.text != nil {
strongSelf.subtitleLabelHeightConstraint?.constant = rect.height + 5.0
} else {
strongSelf.subtitleLabelHeightConstraint?.constant = rect.height
}

})

}


fileprivate func addSubtitleLabel() {

guard let _ = subtitleLabel else {

// Label
subtitleLabel = UILabel()
subtitleLabel?.translatesAutoresizingMaskIntoConstraints = false
subtitleLabel?.backgroundColor = UIColor.clear
subtitleLabel?.textAlignment = .center
subtitleLabel?.numberOfLines = 0
subtitleLabel?.font = UIFont.boldSystemFont(ofSize: UI_USER_INTERFACE_IDIOM() == .pad ? 40.0 : 22.0)
subtitleLabel?.textColor = UIColor.white
subtitleLabel?.numberOfLines = 0;
subtitleLabel?.layer.shadowColor = UIColor.black.cgColor
subtitleLabel?.layer.shadowOffset = CGSize(width: 1.0, height: 1.0);
subtitleLabel?.layer.shadowOpacity = 0.9;
subtitleLabel?.layer.shadowRadius = 1.0;
subtitleLabel?.layer.shouldRasterize = true;
subtitleLabel?.layer.rasterizationScale = UIScreen.main.scale
subtitleLabel?.lineBreakMode = .byWordWrapping
contentOverlayView?.addSubview(subtitleLabel!)

// Position
var constraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|-(20)-[l]-(20)-|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: ["l" : subtitleLabel!])
contentOverlayView?.addConstraints(constraints)
constraints = NSLayoutConstraint.constraints(withVisualFormat: "V:[l]-(30)-|", options: NSLayoutConstraint.FormatOptions(rawValue: 0), metrics: nil, views: ["l" : subtitleLabel!])
contentOverlayView?.addConstraints(constraints)
subtitleLabelHeightConstraint = NSLayoutConstraint(item: subtitleLabel!, attribute: .height, relatedBy: .equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1.0, constant: 30.0)
contentOverlayView?.addConstraint(subtitleLabelHeightConstraint!)

return

}

}

}

0 comments on commit 6698095

Please sign in to comment.