Skip to content

Commit

Permalink
Added SubRip parser without player
Browse files Browse the repository at this point in the history
v2.2
  • Loading branch information
mhergon committed Mar 25, 2017
1 parent b666028 commit 176df02
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 113 deletions.
Binary file not shown.
14 changes: 14 additions & 0 deletions Example/MPMoviePlayerController-Subtitles/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,19 @@ class ViewController: UIViewController {

}

func subtitleParser() {

// Subtitle file
let subtitleFile = Bundle.main.path(forResource: "trailer_720p", ofType: "srt")
let subtitleURL = URL(fileURLWithPath: subtitleFile!)

// Subtitle parser
let parser = Subtitles(file: subtitleURL, encoding: .utf8)

// Do something with result
let subtitles = parser.searchSubtitles(at: 2.0) // Search subtitle at 2.0 seconds

}

}

4 changes: 2 additions & 2 deletions MPMoviePlayerController-Subtitles.podspec
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
Pod::Spec.new do |spec|
spec.name = 'MPMoviePlayerController-Subtitles'
spec.platform = :ios, "8.0"
spec.version = '2.1.1'
spec.version = '2.2'
spec.license = { :type => 'Apache License, Version 2.0' }
spec.homepage = 'https://github.com/mhergon/MPMoviePlayerController-Subtitles'
spec.authors = { 'Marc Hervera' => '[email protected]' }
spec.summary = 'Subtitles made easy'
spec.source = { :git => 'https://github.com/mhergon/MPMoviePlayerController-Subtitles.git', :tag => 'v2.1.1' }
spec.source = { :git => 'https://github.com/mhergon/MPMoviePlayerController-Subtitles.git', :tag => 'v2.2' }
spec.source_files = 'Subtitles.swift'
spec.requires_arc = true
spec.module_name = 'MPMoviePlayerControllerSubtitles'
Expand Down
25 changes: 20 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,12 @@ pod "MPMoviePlayerController-Subtitles"

| Version | Language | Minimum iOS Target |
|:--------------------:|:---------------------------:|:---------------------------:|
| 2.1.x | Swift 3.x | iOS 8 |
| 2.2.x | Swift 3.x | iOS 8 |
| 2.0.x | Swift 2.x | iOS 8 |
| 1.x | Objective-C | iOS 6 |


### Usage

### Usage with player

```swift
import MPMoviePlayerControllerSubtitles
Expand Down Expand Up @@ -63,11 +62,27 @@ moviePlayerView?.moviePlayer.subtitleLabel?.textColor = UIColor.red
moviePlayerView?.moviePlayer.play()
```

## Screenshot
#### Screenshot
<p align="center" >
<img src="https://raw.github.com/mhergon/MPMoviePlayerController-Subtitles/master/assets/screenshot.png" alt="Screenshoot" title="Screenshoot">
</p>

### Usage without player

From version 2.2 you can search text in the SubRip file or text without need play any file.

```swift
// Subtitle file
let subtitleFile = Bundle.main.path(forResource: "trailer_720p", ofType: "srt")
let subtitleURL = URL(fileURLWithPath: subtitleFile!)

// Subtitle parser
let parser = Subtitles(file: subtitleURL, encoding: .utf8)

// Do something with result
let subtitles = parser.searchSubtitles(at: 2.0) // Search subtitle at 2.0 seconds
```

## Contact

- [Linkedin][2]
Expand All @@ -80,4 +95,4 @@ moviePlayerView?.moviePlayer.play()

Licensed under Apache License v2.0.
<br>
Copyright 2015-2016 Marc Hervera.
Copyright 2017 Marc Hervera.
265 changes: 159 additions & 106 deletions Subtitles.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,156 @@ private struct AssociatedKeys {
static var TimerKey = "TimerKey"
}

class Subtitles {

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

// MARK: - Public methods
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)

}

init(subtitles string: String) {

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

}

/// Search subtitles at time
///
/// - Parameter time: Time
/// - Returns: String if exists
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")

// Parsed dict
let parsed = NSMutableDictionary()

// Get groups
let regexStr = "(\\d+)\\n([\\d:,.]+)\\s+-{2}\\>\\s+([\\d:,.]+)\\n([\\s\\S]*?(?=\\n{2,}|$))"
let regex = try NSRegularExpression(pattern: regexStr, options: .caseInsensitive)
let matches = regex.matches(in: payload, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, payload.characters.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.characters.count))
guard let i = match.first 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.characters.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

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)
let 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)
let 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

}

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 MPMoviePlayerController {

//MARK:- Public properties
Expand Down Expand Up @@ -71,7 +221,7 @@ public extension MPMoviePlayerController {
func show(subtitles string: String) {

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

// Timer
timer = Timer.schedule(repeatInterval: 0.5) { (timer) -> Void in self.searchSubtitles() }
Expand Down Expand Up @@ -169,118 +319,21 @@ public extension MPMoviePlayerController {
}

}

fileprivate 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")

// Parsed dict
let parsed = NSMutableDictionary()

// Get groups
let regexStr = "(\\d+)\\n([\\d:,.]+)\\s+-{2}\\>\\s+([\\d:,.]+)\\n([\\s\\S]*?(?=\\n{2,}|$))"
let regex = try NSRegularExpression(pattern: regexStr, options: .caseInsensitive)
let matches = regex.matches(in: payload, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, payload.characters.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.characters.count))
guard let i = match.first 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.characters.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

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)
let 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)
let 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

}

return parsed

} catch {

return nil

}

}


fileprivate func searchSubtitles() {

if playbackState == .playing {

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

guard let values = parsedPayload?.allValues else {
return
}
guard let result = (values as NSArray).filtered(using: predicate).first as? NSDictionary else {
subtitleLabel?.text = ""
return
}
guard let label = subtitleLabel else {
return
}
guard let label = subtitleLabel else { return }

// Set text
label.text = (result["text"] as! String).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
// Search && show subtitles
subtitleLabel?.text = Subtitles.searchSubtitles(parsedPayload, currentPlaybackTime)

// Adjust size
let rect = (label.text! as NSString).boundingRect(with: CGSize(width: label.bounds.width, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName : label.font!], context: nil)
subtitleLabelHeightConstraint?.constant = rect.size.height + 5.0
subtitleContainer?.layoutIfNeeded()
let baseSize = CGSize(width: label.bounds.width, height: CGFloat.greatestFiniteMagnitude)
let rect = label.sizeThatFits(baseSize)
subtitleLabelHeightConstraint?.constant = rect.height + 5.0
subtitleLabel?.layoutIfNeeded()

}

Expand Down

0 comments on commit 176df02

Please sign in to comment.