diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index 399929e6a..5ae45fed6 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -11,8 +11,10 @@ 1A42C28F1C0E3882000F2137 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A42C28E1C0E3882000F2137 /* ViewController.swift */; }; 1A42C2921C0E3883000F2137 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1A42C2901C0E3883000F2137 /* Main.storyboard */; }; 1A42C2941C0E3883000F2137 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1A42C2931C0E3883000F2137 /* Assets.xcassets */; }; - 1A42C29F1C0E39B3000F2137 /* book.epub in Resources */ = {isa = PBXBuildFile; fileRef = 1A42C29E1C0E39B3000F2137 /* book.epub */; }; 1A42C2A21C0E3A8D000F2137 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1A42C2A01C0E3A8D000F2137 /* LaunchScreen.storyboard */; }; + 6E3C4A3F1C3C6708009CBC8C /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E3C4A3E1C3C6708009CBC8C /* MediaPlayer.framework */; }; + 6E5C9B051C4037B7008F6FD9 /* The Silver Chair.epub in Resources */ = {isa = PBXBuildFile; fileRef = 6E5C9B031C4037B7008F6FD9 /* The Silver Chair.epub */; }; + 6E5C9B091C403968008F6FD9 /* The Adventures Of Sherlock Holmes - Adventure I.epub in Resources */ = {isa = PBXBuildFile; fileRef = 6E5C9B081C403942008F6FD9 /* The Adventures Of Sherlock Holmes - Adventure I.epub */; }; F2DF7E310FE62B1F477F22A1 /* Pods.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37A980705BFFE71CCA6F9C31 /* Pods.framework */; }; /* End PBXBuildFile section */ @@ -24,9 +26,17 @@ 1A42C2911C0E3883000F2137 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 1A42C2931C0E3883000F2137 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 1A42C2981C0E3883000F2137 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 1A42C29E1C0E39B3000F2137 /* book.epub */ = {isa = PBXFileReference; lastKnownFileType = file; path = book.epub; sourceTree = ""; }; 1A42C2A11C0E3A8D000F2137 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 37A980705BFFE71CCA6F9C31 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6E3C4A341C3C5181009CBC8C /* 0005-v3.epub */ = {isa = PBXFileReference; lastKnownFileType = file; path = "0005-v3.epub"; sourceTree = ""; }; + 6E3C4A351C3C5181009CBC8C /* 0006-v3.epub */ = {isa = PBXFileReference; lastKnownFileType = file; path = "0006-v3.epub"; sourceTree = ""; }; + 6E3C4A361C3C5181009CBC8C /* 0007-v3.epub */ = {isa = PBXFileReference; lastKnownFileType = file; path = "0007-v3.epub"; sourceTree = ""; }; + 6E3C4A371C3C5181009CBC8C /* 0008-v3.epub */ = {isa = PBXFileReference; lastKnownFileType = file; path = "0008-v3.epub"; sourceTree = ""; }; + 6E3C4A381C3C5181009CBC8C /* 0024-v3.epub */ = {isa = PBXFileReference; lastKnownFileType = file; path = "0024-v3.epub"; sourceTree = ""; }; + 6E3C4A3E1C3C6708009CBC8C /* MediaPlayer.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaPlayer.framework; path = System/Library/Frameworks/MediaPlayer.framework; sourceTree = SDKROOT; }; + 6E5C9B031C4037B7008F6FD9 /* The Silver Chair.epub */ = {isa = PBXFileReference; lastKnownFileType = file; path = "The Silver Chair.epub"; sourceTree = ""; }; + 6E5C9B061C4037DA008F6FD9 /* Coraline.epub */ = {isa = PBXFileReference; lastKnownFileType = file; path = Coraline.epub; sourceTree = ""; }; + 6E5C9B081C403942008F6FD9 /* The Adventures Of Sherlock Holmes - Adventure I.epub */ = {isa = PBXFileReference; lastKnownFileType = file; path = "The Adventures Of Sherlock Holmes - Adventure I.epub"; sourceTree = ""; }; 8BCB174DF233A59B81E828D3 /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -35,6 +45,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 6E3C4A3F1C3C6708009CBC8C /* MediaPlayer.framework in Frameworks */, F2DF7E310FE62B1F477F22A1 /* Pods.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -69,14 +80,38 @@ 1A42C2931C0E3883000F2137 /* Assets.xcassets */, 1A42C2981C0E3883000F2137 /* Info.plist */, 1A42C2A01C0E3A8D000F2137 /* LaunchScreen.storyboard */, - 1A42C29E1C0E39B3000F2137 /* book.epub */, + 6E5C9B011C4037B7008F6FD9 /* Sample eBooks */, + 6E3C4A331C3C5181009CBC8C /* bsa-epubs */, ); path = Example; sourceTree = ""; }; + 6E3C4A331C3C5181009CBC8C /* bsa-epubs */ = { + isa = PBXGroup; + children = ( + 6E5C9B061C4037DA008F6FD9 /* Coraline.epub */, + 6E3C4A341C3C5181009CBC8C /* 0005-v3.epub */, + 6E3C4A351C3C5181009CBC8C /* 0006-v3.epub */, + 6E3C4A361C3C5181009CBC8C /* 0007-v3.epub */, + 6E3C4A371C3C5181009CBC8C /* 0008-v3.epub */, + 6E3C4A381C3C5181009CBC8C /* 0024-v3.epub */, + ); + path = "bsa-epubs"; + sourceTree = ""; + }; + 6E5C9B011C4037B7008F6FD9 /* Sample eBooks */ = { + isa = PBXGroup; + children = ( + 6E5C9B081C403942008F6FD9 /* The Adventures Of Sherlock Holmes - Adventure I.epub */, + 6E5C9B031C4037B7008F6FD9 /* The Silver Chair.epub */, + ); + path = "Sample eBooks"; + sourceTree = ""; + }; D394FAB8B11D7C692E7BE00D /* Frameworks */ = { isa = PBXGroup; children = ( + 6E3C4A3E1C3C6708009CBC8C /* MediaPlayer.framework */, 37A980705BFFE71CCA6F9C31 /* Pods.framework */, ); name = Frameworks; @@ -154,7 +189,8 @@ files = ( 1A42C2941C0E3883000F2137 /* Assets.xcassets in Resources */, 1A42C2A21C0E3A8D000F2137 /* LaunchScreen.storyboard in Resources */, - 1A42C29F1C0E39B3000F2137 /* book.epub in Resources */, + 6E5C9B091C403968008F6FD9 /* The Adventures Of Sherlock Holmes - Adventure I.epub in Resources */, + 6E5C9B051C4037B7008F6FD9 /* The Silver Chair.epub in Resources */, 1A42C2921C0E3883000F2137 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -319,6 +355,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.1; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; diff --git a/Example/Example/Base.lproj/Main.storyboard b/Example/Example/Base.lproj/Main.storyboard index 5b6797fa1..449b42dc2 100644 --- a/Example/Example/Base.lproj/Main.storyboard +++ b/Example/Example/Base.lproj/Main.storyboard @@ -1,7 +1,8 @@ - + - + + @@ -16,20 +17,29 @@ - + - - + + diff --git a/Example/Example/Sample eBooks/The Adventures Of Sherlock Holmes - Adventure I.epub b/Example/Example/Sample eBooks/The Adventures Of Sherlock Holmes - Adventure I.epub new file mode 100644 index 000000000..177edc44e Binary files /dev/null and b/Example/Example/Sample eBooks/The Adventures Of Sherlock Holmes - Adventure I.epub differ diff --git a/Example/Example/book.epub b/Example/Example/Sample eBooks/The Silver Chair.epub similarity index 100% rename from Example/Example/book.epub rename to Example/Example/Sample eBooks/The Silver Chair.epub diff --git a/Example/Example/ViewController.swift b/Example/Example/ViewController.swift old mode 100644 new mode 100755 index 7605e4333..04e27f059 --- a/Example/Example/ViewController.swift +++ b/Example/Example/ViewController.swift @@ -17,14 +17,28 @@ class ViewController: UIViewController { } @IBAction func didOpen(sender: AnyObject) { + openEpub(sender.tag); + } + + func openEpub(sampleNum:Int) { let config = FolioReaderConfig() -// config.shouldHideNavigationOnTap = false + config.shouldHideNavigationOnTap = false +// config.allowSharing = false // config.toolBarTintColor = UIColor.redColor() // config.toolBarBackgroundColor = UIColor.purpleColor() // config.menuTextColor = UIColor.brownColor() // config.menuBackgroundColor = UIColor.lightGrayColor() - let bookPath = NSBundle.mainBundle().pathForResource("book", ofType: "epub") + // http://www.readbeyond.it/ebooks.html + let epubSampleFiles = [ + "The Silver Chair", // standard eBook + "The Adventures Of Sherlock Holmes - Adventure I", // audio-eBook + ] + + let epubName = epubSampleFiles[sampleNum-1]; + let bookPath = NSBundle.mainBundle().pathForResource(epubName, ofType: "epub") + FolioReader.presentReader(parentViewController: self, withEpubPath: bookPath!, andConfig: config) } + } \ No newline at end of file diff --git a/Source/EPUBCore/FRBook.swift b/Source/EPUBCore/FRBook.swift index 489bbd327..1b3d736ab 100755 --- a/Source/EPUBCore/FRBook.swift +++ b/Source/EPUBCore/FRBook.swift @@ -3,6 +3,7 @@ // FolioReaderKit // // Created by Heberti Almeida on 09/04/15. +// Extended by Kevin Jantzer on 12/30/15 // Copyright (c) 2015 Folio Reader. All rights reserved. // @@ -12,8 +13,67 @@ class FRBook: NSObject { var resources = FRResources() var metadata = FRMetadata() var spine = FRSpine() + var smils = FRSmils() var tableOfContents: [FRTocReference]! var opfResource: FRResource! var ncxResource: FRResource! var coverImage: FRResource! + + func hasAudio() -> Bool { + return smils.smils.count > 0 ? true : false; + } + + func title() -> String! { + return metadata.titles[0] + } + + // MARK: - Media Overlay Metadata + // http://www.idpf.org/epub/301/spec/epub-mediaoverlays.html#sec-package-metadata + + func duration() -> String? { + return metadata.findMetaByProperty("media:duration"); + } + + // @NOTE: should "#" be automatically prefixed with the ID? + func durationFor(ID: String) -> String? { + return metadata.findMetaByProperty("media:duration", refinedBy: ID) + } + + + func activeClass() -> String! { + let className = metadata.findMetaByProperty("media:active-class"); + return className != nil ? className : "epub-media-overlay-active"; + } + + func playbackActiveClass() -> String! { + let className = metadata.findMetaByProperty("media:playback-active-class"); + return className != nil ? className : "epub-media-overlay-playing"; + } + + + // MARK: - Media Overlay (SMIL) retrieval + + /** + Get Smil File from a resource (if it has a media-overlay) + */ + func smilFileForResource(resource: FRResource!) -> FRSmilFile! { + if( resource == nil || resource.mediaOverlay == nil ){ + return nil + } + + // lookup the smile resource to get info about the file + let smilResource = resources.getById(resource.mediaOverlay) + + // use the resource to get the file + return smils.getByHref( smilResource!.href ) + } + + func smilFileForHref(href: String) -> FRSmilFile! { + return smilFileForResource(resources.getByHref(href)) + } + + func smilFileForId(ID: String) -> FRSmilFile! { + return smilFileForResource(resources.getById(ID)) + } + } diff --git a/Source/EPUBCore/FREpubParser.swift b/Source/EPUBCore/FREpubParser.swift index 65649efb5..364ec468a 100755 --- a/Source/EPUBCore/FREpubParser.swift +++ b/Source/EPUBCore/FREpubParser.swift @@ -77,15 +77,26 @@ class FREpubParser: NSObject, SSZipArchiveDelegate { do { let xmlDoc = try AEXMLDocument(xmlData: opfData!) + + // parse and save each "manifest item" for item in xmlDoc.root["manifest"]["item"].all! { let resource = FRResource() resource.id = item.attributes["id"] resource.href = item.attributes["href"] resource.fullHref = (resourcesBasePath as NSString).stringByAppendingPathComponent(item.attributes["href"]!).stringByRemovingPercentEncoding resource.mediaType = FRMediaType.mediaTypesByName[item.attributes["media-type"]!] + resource.mediaOverlay = item.attributes["media-overlay"] + + // if a .smil file is listed in resources, go parse that file now and save it on book model + if( resource.mediaType != nil && resource.mediaType == FRMediaType.SMIL ){ + readSmilFile(resource); + } + book.resources.add(resource) } + book.smils.basePath = resourcesBasePath + // Get the first resource with the NCX mediatype book.ncxResource = book.resources.findFirstResource(byMediaType: FRMediaType.NCX) @@ -112,6 +123,50 @@ class FREpubParser: NSObject, SSZipArchiveDelegate { } } + /** + Reads and parses a .smil file + */ + private func readSmilFile(resource: FRResource){ + let smilData = try? NSData(contentsOfFile: resource.fullHref, options: .DataReadingMappedAlways) + + var smilFile = FRSmilFile(resource: resource); + + do { + let xmlDoc = try AEXMLDocument(xmlData: smilData!) + + let children = xmlDoc.root["body"].children + + if( children.count > 0 ){ + smilFile.data.appendContentsOf( readSmilFileElements(children) ) + } + + } catch { + print("Cannot read .smil file: "+resource.href) + } + + book.smils.add(smilFile); + } + + private func readSmilFileElements(children:[AEXMLElement]) -> [FRSmilElement] { + + var data = [FRSmilElement]() + + // convert each smil element to a FRSmil object + for item in children { + + let smil = FRSmilElement(name: item.name, attributes: item.attributes) + + // if this element has children, convert them to objects too + if( item.children.count > 0 ){ + smil.children.appendContentsOf( readSmilFileElements(item.children) ) + } + + data.append(smil) + } + + return data + } + /** Read and parse the Table of Contents. */ @@ -209,8 +264,14 @@ class FREpubParser: NSObject, SSZipArchiveDelegate { if tag.attributes["property"] != nil && tag.attributes["id"] != nil { metadata.metaAttributes.append(Meta(id: tag.attributes["id"]!, property: tag.attributes["property"]!, value: tag.value ?? "")) } + + if tag.attributes["property"] != nil { + metadata.metaAttributes.append(Meta(property: tag.attributes["property"]!, value: tag.value != nil ? tag.value! : "", refines: tag.attributes["refines"] != nil ? tag.attributes["refines"] : nil)) + } + } } + return metadata } diff --git a/Source/EPUBCore/FRMetadata.swift b/Source/EPUBCore/FRMetadata.swift index 2640c28a2..f5a2aa4ba 100755 --- a/Source/EPUBCore/FRMetadata.swift +++ b/Source/EPUBCore/FRMetadata.swift @@ -58,6 +58,7 @@ struct Meta { var id: String? var property: String? var value: String? + var refines: String? init(name: String, content: String) { self.name = name @@ -69,6 +70,12 @@ struct Meta { self.property = property self.value = value } + + init(property: String, value: String, refines: String!) { + self.property = property + self.value = value + self.refines = refines + } } /** @@ -102,4 +109,27 @@ class FRMetadata: NSObject { } return nil } + + func findMetaByProperty(property: String, refinedBy: String?) -> String? { + if property.isEmpty { + return nil + } + + for meta in metaAttributes { + if meta.property != nil { + if( meta.property == property && refinedBy == nil && meta.refines == nil){ + return meta.value + } + if( meta.property == property && meta.refines == refinedBy){ + return meta.value + } + } + } + return nil + } + + func findMetaByProperty(property: String) -> String? { + return findMetaByProperty(property, refinedBy: nil); + } + } diff --git a/Source/EPUBCore/FRResource.swift b/Source/EPUBCore/FRResource.swift index e33e8eb91..36586f0ea 100755 --- a/Source/EPUBCore/FRResource.swift +++ b/Source/EPUBCore/FRResource.swift @@ -14,5 +14,13 @@ class FRResource: NSObject { var href: String! var fullHref: String! var mediaType: MediaType! + var mediaOverlay: String! var inputEncoding: String! + + func basePath() -> String! { + if href == nil || href.isEmpty { return nil } + var paths = fullHref.componentsSeparatedByString("/") + paths.removeLast() + return paths.joinWithSeparator("/") + } } diff --git a/Source/EPUBCore/FRResources.swift b/Source/EPUBCore/FRResources.swift index 81454b4f7..7cb32608c 100755 --- a/Source/EPUBCore/FRResources.swift +++ b/Source/EPUBCore/FRResources.swift @@ -26,7 +26,7 @@ class FRResources: NSObject { */ func findFirstResource(byMediaType mediaType: MediaType) -> FRResource? { for resource in resources.values { - if resource.mediaType == mediaType { + if resource.mediaType != nil && resource.mediaType == mediaType { return resource } } diff --git a/Source/EPUBCore/FRSmilElement.swift b/Source/EPUBCore/FRSmilElement.swift new file mode 100644 index 000000000..e7fd7d207 --- /dev/null +++ b/Source/EPUBCore/FRSmilElement.swift @@ -0,0 +1,113 @@ +// +// FRSmil.swift +// Pods +// +// Created by Kevin Jantzer on 12/30/15. +// +// + +import UIKit + +// Media Overlay Documentation +// http://www.idpf.org/accessibility/guidelines/content/overlays/overview.php#mo005-samp + + +class FRSmilElement: NSObject { + var name: String // the name of the tag: , , ,