Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package services
- import captioning.{CaptionString, CaptioningClient}
- import captioning.CaptionString.CaptionType
- import captioning.CaptioningClient.CaptioningJob
- import captioning.ast.ASTClient
- import captioning.cielo24.CieloClient
- import captioning.threePlay.ThreePlayClient
- import controllers.routes
- import global.{AppConfig, ErrorHandler}
- import models.media.{CaptionError, CaptioningStatus, CaptionsProcessing, HasCaptions}
- import models.{CaptioningProfile, CaptioningProvider, Media}
- import org.joda.time.Instant
- import search.SimpleContentSearch
- import util.{ExecutionContexts, SubtitleHelpers}
- import util.exceptions.{FailedToUploadToS3, UnsupportedCaptioningProvider}
- import scala.concurrent.duration._
- import scala.io.Source
- import java.io.{ByteArrayInputStream, File, FileInputStream, InputStream}
- import java.net.URI
- import java.io.File
- import java.util.UUID
- import scala.concurrent.{ExecutionContext, Future}
- import com.google.inject.{ImplementedBy, Inject, Singleton}
- //trait CaptioningService {
- // this: AppConfig
- // with AwsS3
- // with CaptioningProfileService
- // with ASTClient
- // with CieloClient
- // with ThreePlayClient
- // with MediaService
- // with SimpleContentSearch
- // with ExecutionContexts =>
- //
- // val captioningService: CaptioningService
- @ImplementedBy(classOf[CaptioningServiceImpl])
- trait CaptioningService extends BaseService {
- def createCaptioningJob(mediaId: String): Future[CaptioningJob]
- def addCaptionToMedia(mediaId: String, caption: CaptionString): Future[Media]
- def addCaptionToMedia(mediaId: String, job: CaptioningJob, postData: JsValue = JsString("")): Future[Media]
- def addCaptionToMedia(mediaId: String, fileUrl: URI): Future[Media]
- def recordCaptioningError(mediaId: String, errorMsg: String): Future[Unit]
- def verifyCredentials(profile: CaptioningProfile): Future[Unit]
- def indexCaptions(media: Media): Future[Unit] = Future
- }
- @Singleton
- class CaptioningServiceImpl @Inject()(
- val captioningProfileService: CaptioningProfileService,
- val awsS3: AwsS3,
- val astClient: ASTClient,
- val cieloClient: CieloClient,
- val threePlayClient: ThreePlayClient,
- val simpleContextSearch: SimpleContextSearch,
- val mediaService: MediaService
- )(
- implicit val executionContext: ExecutionContext
- ) extends BaseService with CaptioningService {
- private def clientForProfile(profile: CaptioningProfile): Future[CaptioningClient] =
- profile.provider match {
- case CaptioningProvider.AST =>
- Future.successful(astClient)
- case CaptioningProvider.Cielo24 =>
- Future.successful(cieloClient)
- case CaptioningProvider.ThreePlay =>
- Future.successful(threePlayClient)
- case _ =>
- Future.failed(new ServiceException(UnsupportedCaptioningProvider(profile.provider.toString)))
- }
- def createCaptioningJob(mediaId: String): Future[CaptioningJob] =
- for {
- media <- mediaService.fetchById(mediaId)
- captioningProfile <- captioningProfileService.findOrCreateByInstitutionId(media.institutionId)
- job <- requestCaptioning(captioningProfile, media.audioUrlOrFail, mediaId)
- _ <- mediaService.updateCaptionStatus(media, CaptionsProcessing(Instant.now()))
- } yield job
- def addCaptionToMedia(mediaId: String, caption: CaptionString): Future[Media] =
- (for {
- media <- mediaService.fetchById(mediaId)
- webVTTCaption = caption match {
- case CaptionString(content, CaptionType.SRT) => CaptionString.fromString(SubtitleHelpers.convertSrt2WebVTT(content))
- case CaptionString(content, CaptionType.WebVTT) => caption
- }
- captionS3UrlOpt <- logRecover(uploadToS3(webVTTCaption, media))
- captionS3Url = captionS3UrlOpt getOrElse (throw ServiceException(FailedToUploadToS3))
- updatedMedia <- mediaService.updateMediaWithCaptionUrl(media, captionS3Url)
- } yield {
- logRecover(indexCaptions(updatedMedia, webVTTCaption.content))
- updatedMedia
- }).recover({
- case e: Throwable =>
- mediaService.fetchById(mediaId).map { media =>
- mediaService.updateCaptionStatus(media, CaptionError(e.getMessage))
- }
- throw e
- })
- def addCaptionToMedia(mediaId: String, job: CaptioningJob, postData: JsValue = JsString("")): Future[Media] =
- (for {
- media <- mediaService.fetchById(mediaId)
- defaultProfile <- captioningProfileService.findOrCreateByInstitutionId(media.institutionId)
- client <- clientForProfile(defaultProfile)
- caption <- util.FutureHelpers.retry(client.getCaptions(defaultProfile, job), Seq(5.seconds, 1.minutes, 5.minutes, 10.minutes, 30.minutes), 5)(Akka.system.scheduler)
- updatedMedia <- addCaptionToMedia(mediaId,caption)
- } yield {
- updatedMedia
- }) recoverWith {
- case e: ThreePlayError => {
- val error = new ThreePlayError(s"Error ${e.toString} posting callback url ${routes.Captioning.jobCompleteThreePlay(appConfig.CaptioningThreePlay.callbackToken, mediaId, job.id).url} with data ${postData}")
- ErrorHandler.notifyAirbrake(error)
- Future.failed(error)
- }
- }
- def addCaptionToMedia(mediaId: String, fileUrl: URI): Future[Media] = {
- val tempFile = File.createTempFile("captions-" + UUID.randomUUID().toString(), ".vtt")
- for {
- file <- awsS3.get(fileUrl, tempFile)
- fin = Source.fromFile(file).mkString
- _ = file.delete()
- caption = new CaptionString(fin, CaptionType.WebVTT )
- updatedMedia <- addCaptionToMedia(mediaId,caption)
- } yield {
- updatedMedia
- }
- }
- def recordCaptioningError(mediaId: String, errorMsg: String): Future[Unit] =
- mediaService.fetchById(mediaId) flatMap { media =>
- media.videoPresentation match {
- case Some(video) =>
- video.captions match {
- case Some(HasCaptions(_)) => Future.successful(None)
- case _ => mediaService.updateCaptionStatus(media, CaptionError(errorMsg)).map(_ => ())
- }
- case _ =>
- Logger.warn(s"A Non-video Media was sent to CaptioningService.recordCaptioningError: $mediaId")
- Future.successful(())
- }
- }
- def verifyCredentials(profile: CaptioningProfile): Future[Unit] = for {
- client <- clientForProfile(profile)
- _ <- client.verifyCredentials(profile)
- } yield ()
- def indexCaptions(media: Media): Future[Unit] = Future {
- media.videoPresentation map { video =>
- video.captions map (indexCaptions(media, _))
- }
- }
- def indexCaptions(media: Media, status: CaptioningStatus): Future[Unit] = status match {
- case HasCaptions(uri) => indexCaptions(media, uri)
- case _ => Future.successful(())
- }
- def indexCaptions(media: Media, fileString: String): Future[Unit] = {
- Logger.info(s"[CaptioningService] [${media.getIdOrFail}] indexing captions")
- val stream = new ByteArrayInputStream(fileString.getBytes("UTF-8"))
- indexCaptions(media, stream)
- }
- def indexCaptions(media: Media, uri: URI): Future[Unit] = Future {
- if (uri.toString.nonEmpty) {
- val tempFile = File.createTempFile("captions-" + UUID.randomUUID().toString(), ".vtt")
- Logger.info(s"[CaptioningService] [${media.getIdOrFail}] downloading captions from ${uri.toString}")
- try {
- (for {
- file <- awsS3.get(uri, tempFile)
- fileStream = new FileInputStream(file)
- _ <- indexCaptions(media, fileStream)
- } yield ()) andThen { case _ => tempFile.delete }
- } catch {
- case ServiceException(InvalidS3Uri) =>
- Logger.error(s"[CaptioningService] [${media.getIdOrFail}] error downloading captions from ${uri.toString}")
- }
- } else {
- Logger.warn(s"[CaptioningService] [${media.getIdOrFail}] media has empty captions url")
- }
- }
- def indexCaptions(media: Media, inputStream: InputStream): Future[Unit] = Future {
- Logger.info(s"[CaptioningService] [${media.getIdOrFail}] parsing captions")
- val webVTTParser = new util.WebvttParser()
- val cues = webVTTParser.parse(inputStream, media.id)
- Logger.info(s"[CaptioningService] [${media.getIdOrFail}] indexing captions")
- for {
- _ <- simpleContentSearch.updateForVideo(media, cues)
- } yield ()
- }
- private def requestCaptioning(profile: CaptioningProfile, audioUrl: URI, mediaId: String): Future[CaptioningJob] =
- for {
- client <- clientForProfile(profile)
- job <- client.startCaptioning(profile, mediaId, audioUrl.toString)
- } yield job
- private def uploadToS3(caption: CaptionString, media: Media): Future[URI] = {
- import java.io._
- val mediaId = media.getIdOrFail
- val tempFile = File.createTempFile(s"$mediaId/caption.vtt", "vtt")
- val writer = new BufferedWriter(new PrintWriter(tempFile))
- writer.write(caption.content)
- writer.close()
- val mediaFolder = media.mediaFolder(appConfig.AwsContentUploadBucket)
- val mediaFile = new URI(mediaFolder.toString + "/caption.vtt")
- Logger.info(s"[CaptioningService] [${media.getIdOrFail}] uploading captions to S3: ${mediaFile.toString}")
- val fut = awsS3.put(mediaFile, tempFile) map { _ =>
- mediaFile
- }
- // We want this to happen even on an upload error
- fut andThen { case _ => tempFile.delete() }
- fut
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement