Advertisement
Guest User

Untitled

a guest
Jan 19th, 2017
85
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.99 KB | None | 0 0
  1. package services
  2.  
  3. import captioning.{CaptionString, CaptioningClient}
  4. import captioning.CaptionString.CaptionType
  5. import captioning.CaptioningClient.CaptioningJob
  6. import captioning.ast.ASTClient
  7. import captioning.cielo24.CieloClient
  8. import captioning.threePlay.ThreePlayClient
  9. import controllers.routes
  10. import global.{AppConfig, ErrorHandler}
  11. import models.media.{CaptionError, CaptioningStatus, CaptionsProcessing, HasCaptions}
  12. import models.{CaptioningProfile, CaptioningProvider, Media}
  13. import org.joda.time.Instant
  14. import search.SimpleContentSearch
  15. import util.{ExecutionContexts, SubtitleHelpers}
  16. import util.exceptions.{FailedToUploadToS3, UnsupportedCaptioningProvider}
  17.  
  18. import scala.concurrent.duration._
  19. import scala.io.Source
  20. import java.io.{ByteArrayInputStream, File, FileInputStream, InputStream}
  21. import java.net.URI
  22. import java.io.File
  23. import java.util.UUID
  24.  
  25. import scala.concurrent.{ExecutionContext, Future}
  26. import com.google.inject.{ImplementedBy, Inject, Singleton}
  27.  
  28. //trait CaptioningService {
  29. // this: AppConfig
  30. // with AwsS3
  31. // with CaptioningProfileService
  32. // with ASTClient
  33. // with CieloClient
  34. // with ThreePlayClient
  35. // with MediaService
  36. // with SimpleContentSearch
  37. // with ExecutionContexts =>
  38. //
  39. // val captioningService: CaptioningService
  40.  
  41. @ImplementedBy(classOf[CaptioningServiceImpl])
  42. trait CaptioningService extends BaseService {
  43. def createCaptioningJob(mediaId: String): Future[CaptioningJob]
  44. def addCaptionToMedia(mediaId: String, caption: CaptionString): Future[Media]
  45. def addCaptionToMedia(mediaId: String, job: CaptioningJob, postData: JsValue = JsString("")): Future[Media]
  46. def addCaptionToMedia(mediaId: String, fileUrl: URI): Future[Media]
  47. def recordCaptioningError(mediaId: String, errorMsg: String): Future[Unit]
  48. def verifyCredentials(profile: CaptioningProfile): Future[Unit]
  49. def indexCaptions(media: Media): Future[Unit] = Future
  50. }
  51.  
  52. @Singleton
  53. class CaptioningServiceImpl @Inject()(
  54. val captioningProfileService: CaptioningProfileService,
  55. val awsS3: AwsS3,
  56. val astClient: ASTClient,
  57. val cieloClient: CieloClient,
  58. val threePlayClient: ThreePlayClient,
  59. val simpleContextSearch: SimpleContextSearch,
  60. val mediaService: MediaService
  61. )(
  62. implicit val executionContext: ExecutionContext
  63. ) extends BaseService with CaptioningService {
  64.  
  65. private def clientForProfile(profile: CaptioningProfile): Future[CaptioningClient] =
  66. profile.provider match {
  67. case CaptioningProvider.AST =>
  68. Future.successful(astClient)
  69. case CaptioningProvider.Cielo24 =>
  70. Future.successful(cieloClient)
  71. case CaptioningProvider.ThreePlay =>
  72. Future.successful(threePlayClient)
  73. case _ =>
  74. Future.failed(new ServiceException(UnsupportedCaptioningProvider(profile.provider.toString)))
  75. }
  76.  
  77. def createCaptioningJob(mediaId: String): Future[CaptioningJob] =
  78. for {
  79. media <- mediaService.fetchById(mediaId)
  80. captioningProfile <- captioningProfileService.findOrCreateByInstitutionId(media.institutionId)
  81. job <- requestCaptioning(captioningProfile, media.audioUrlOrFail, mediaId)
  82. _ <- mediaService.updateCaptionStatus(media, CaptionsProcessing(Instant.now()))
  83. } yield job
  84.  
  85. def addCaptionToMedia(mediaId: String, caption: CaptionString): Future[Media] =
  86. (for {
  87. media <- mediaService.fetchById(mediaId)
  88. webVTTCaption = caption match {
  89. case CaptionString(content, CaptionType.SRT) => CaptionString.fromString(SubtitleHelpers.convertSrt2WebVTT(content))
  90. case CaptionString(content, CaptionType.WebVTT) => caption
  91. }
  92. captionS3UrlOpt <- logRecover(uploadToS3(webVTTCaption, media))
  93. captionS3Url = captionS3UrlOpt getOrElse (throw ServiceException(FailedToUploadToS3))
  94. updatedMedia <- mediaService.updateMediaWithCaptionUrl(media, captionS3Url)
  95. } yield {
  96. logRecover(indexCaptions(updatedMedia, webVTTCaption.content))
  97. updatedMedia
  98. }).recover({
  99. case e: Throwable =>
  100. mediaService.fetchById(mediaId).map { media =>
  101. mediaService.updateCaptionStatus(media, CaptionError(e.getMessage))
  102. }
  103. throw e
  104. })
  105.  
  106. def addCaptionToMedia(mediaId: String, job: CaptioningJob, postData: JsValue = JsString("")): Future[Media] =
  107. (for {
  108. media <- mediaService.fetchById(mediaId)
  109. defaultProfile <- captioningProfileService.findOrCreateByInstitutionId(media.institutionId)
  110. client <- clientForProfile(defaultProfile)
  111. caption <- util.FutureHelpers.retry(client.getCaptions(defaultProfile, job), Seq(5.seconds, 1.minutes, 5.minutes, 10.minutes, 30.minutes), 5)(Akka.system.scheduler)
  112. updatedMedia <- addCaptionToMedia(mediaId,caption)
  113. } yield {
  114. updatedMedia
  115. }) recoverWith {
  116. case e: ThreePlayError => {
  117. val error = new ThreePlayError(s"Error ${e.toString} posting callback url ${routes.Captioning.jobCompleteThreePlay(appConfig.CaptioningThreePlay.callbackToken, mediaId, job.id).url} with data ${postData}")
  118. ErrorHandler.notifyAirbrake(error)
  119. Future.failed(error)
  120. }
  121. }
  122.  
  123. def addCaptionToMedia(mediaId: String, fileUrl: URI): Future[Media] = {
  124. val tempFile = File.createTempFile("captions-" + UUID.randomUUID().toString(), ".vtt")
  125. for {
  126. file <- awsS3.get(fileUrl, tempFile)
  127. fin = Source.fromFile(file).mkString
  128. _ = file.delete()
  129. caption = new CaptionString(fin, CaptionType.WebVTT )
  130. updatedMedia <- addCaptionToMedia(mediaId,caption)
  131. } yield {
  132. updatedMedia
  133. }
  134. }
  135.  
  136. def recordCaptioningError(mediaId: String, errorMsg: String): Future[Unit] =
  137. mediaService.fetchById(mediaId) flatMap { media =>
  138. media.videoPresentation match {
  139. case Some(video) =>
  140. video.captions match {
  141. case Some(HasCaptions(_)) => Future.successful(None)
  142. case _ => mediaService.updateCaptionStatus(media, CaptionError(errorMsg)).map(_ => ())
  143. }
  144. case _ =>
  145. Logger.warn(s"A Non-video Media was sent to CaptioningService.recordCaptioningError: $mediaId")
  146. Future.successful(())
  147. }
  148. }
  149.  
  150. def verifyCredentials(profile: CaptioningProfile): Future[Unit] = for {
  151. client <- clientForProfile(profile)
  152. _ <- client.verifyCredentials(profile)
  153. } yield ()
  154.  
  155. def indexCaptions(media: Media): Future[Unit] = Future {
  156. media.videoPresentation map { video =>
  157. video.captions map (indexCaptions(media, _))
  158. }
  159. }
  160.  
  161. def indexCaptions(media: Media, status: CaptioningStatus): Future[Unit] = status match {
  162. case HasCaptions(uri) => indexCaptions(media, uri)
  163. case _ => Future.successful(())
  164. }
  165.  
  166. def indexCaptions(media: Media, fileString: String): Future[Unit] = {
  167. Logger.info(s"[CaptioningService] [${media.getIdOrFail}] indexing captions")
  168. val stream = new ByteArrayInputStream(fileString.getBytes("UTF-8"))
  169. indexCaptions(media, stream)
  170. }
  171.  
  172. def indexCaptions(media: Media, uri: URI): Future[Unit] = Future {
  173. if (uri.toString.nonEmpty) {
  174. val tempFile = File.createTempFile("captions-" + UUID.randomUUID().toString(), ".vtt")
  175. Logger.info(s"[CaptioningService] [${media.getIdOrFail}] downloading captions from ${uri.toString}")
  176. try {
  177. (for {
  178. file <- awsS3.get(uri, tempFile)
  179. fileStream = new FileInputStream(file)
  180. _ <- indexCaptions(media, fileStream)
  181. } yield ()) andThen { case _ => tempFile.delete }
  182. } catch {
  183. case ServiceException(InvalidS3Uri) =>
  184. Logger.error(s"[CaptioningService] [${media.getIdOrFail}] error downloading captions from ${uri.toString}")
  185. }
  186. } else {
  187. Logger.warn(s"[CaptioningService] [${media.getIdOrFail}] media has empty captions url")
  188. }
  189. }
  190.  
  191. def indexCaptions(media: Media, inputStream: InputStream): Future[Unit] = Future {
  192. Logger.info(s"[CaptioningService] [${media.getIdOrFail}] parsing captions")
  193. val webVTTParser = new util.WebvttParser()
  194. val cues = webVTTParser.parse(inputStream, media.id)
  195. Logger.info(s"[CaptioningService] [${media.getIdOrFail}] indexing captions")
  196. for {
  197. _ <- simpleContentSearch.updateForVideo(media, cues)
  198. } yield ()
  199. }
  200.  
  201. private def requestCaptioning(profile: CaptioningProfile, audioUrl: URI, mediaId: String): Future[CaptioningJob] =
  202. for {
  203. client <- clientForProfile(profile)
  204. job <- client.startCaptioning(profile, mediaId, audioUrl.toString)
  205. } yield job
  206.  
  207. private def uploadToS3(caption: CaptionString, media: Media): Future[URI] = {
  208. import java.io._
  209.  
  210. val mediaId = media.getIdOrFail
  211. val tempFile = File.createTempFile(s"$mediaId/caption.vtt", "vtt")
  212. val writer = new BufferedWriter(new PrintWriter(tempFile))
  213. writer.write(caption.content)
  214. writer.close()
  215.  
  216. val mediaFolder = media.mediaFolder(appConfig.AwsContentUploadBucket)
  217. val mediaFile = new URI(mediaFolder.toString + "/caption.vtt")
  218. Logger.info(s"[CaptioningService] [${media.getIdOrFail}] uploading captions to S3: ${mediaFile.toString}")
  219. val fut = awsS3.put(mediaFile, tempFile) map { _ =>
  220. mediaFile
  221. }
  222. // We want this to happen even on an upload error
  223. fut andThen { case _ => tempFile.delete() }
  224. fut
  225. }
  226. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement