All Downloads are FREE. Search and download functionalities are using the official Maven repository.

clarifai.Clarifai.scala Maven / Gradle / Ivy

The newest version!
package clarifai

/** Imports scalaj library for handling HTTP request & response. */
import scalaj.http._
import scala.util.parsing.json.JSON

/** Configuration setting for the client wrapper. */
object Config {
  val baseURL = "https://api.clarifai.com"
  val version = "v1"
}

/** Client wrapper for accessing Clarifai endpoints.
  *
  * Currently supports the following endpoints:
  * - Tag
  * - Feedback
  * - Color (beta)
  * - Info
  * - Usage
  * You would need a Clarifai developer account for using the service.
  * Developer page: https://developer.clarifai.com
  *
  * @constructor create a new client object for accessing endpoints.
  * @param id client id for your Clarifai API applicaiton
  * @param secret client secret for your Clarifai API applicaiton
  */
class ClarifaiClient(id: String, secret: String) {
  val clientID = id
  val clientSecret = secret
  var accessToken = "unassigned"
  var throttled = false

  /** High-level functions for accessing Clarifai endpoints.
    * 
    * The following functions allow users to access the Clarifai service directly
    * and receive the response in JSON-like class structure.
    */

  /** Feedback - provides the ability to give feedback to the API about images and videos that were previously tagged.
    * 
    * @param Map of values for providing feedback to the API
    * @return Feedback response from the Clarifai endpoint 
    */
  def feedback(feedbackReq: Map[String, Any]): Either[Option[String], FeedbackResp] = {
    if ((!feedbackReq.contains("url") || feedbackReq.get("url").get.asInstanceOf[Array[String]].length < 1 )
      && (!feedbackReq.contains("docids") || feedbackReq.get("docids").get.asInstanceOf[Array[String]].length < 1 )) {
      return Left(Some("Needs at least one url or docid"))
    }

    if (feedbackReq.contains("url") && feedbackReq.contains("docids")) {
      return Left(Some("Request must provide exactly one of the following fields: urls or docids"))
    }

    /** Converts the user input into string format. */
    var data = ""
    data = data.concat(_extractStrArrWithAmp(feedbackReq, "add_tags"))
    data = data.concat(_extractStrArrWithAmp(feedbackReq, "remove_tags"))
    data = data.concat(_extractStrArrWithAmp(feedbackReq, "similar_docids"))
    data = data.concat(_extractStrArrWithAmp(feedbackReq, "similar_url"))
    data = data.concat(_extractStrArrWithAmp(feedbackReq, "dissimilar_docids"))
    data = data.concat(_extractStrArrWithAmp(feedbackReq, "dissimilar_url"))
    data = data.concat(_extractStrArrWithAmp(feedbackReq, "search_click"))
    data = data.concat(_extractStrArrWithAmp(feedbackReq, "url"))
    data = data.concat(_extractStrArrWithAmp(feedbackReq, "docids"))
    data = data.dropRight(1) // Removes the last "&" character.

    /** Sends the HTTP request. */
    val response = _commonHTTPRequest(Some(data), "feedback", "POST", false)

    /** Returns the HTTP response into JSON-like class structure. */
    response match {
      case Left(err) => Left(err)
      case Right(result) => {
        val rmap = JSON.parseFull(result).get.asInstanceOf[Map[String, Any]]

        Right(
          FeedbackResp(
            rmap.get("status_code").get.asInstanceOf[String],
            rmap.get("status_msg").get.asInstanceOf[String]
          )
        )
      }
    }
  }

  /** Color (beta) - retrieves the dominant colors present in your images or videos. 
    * 
    * @param Array of urls for color detection
    * @return Color response from the Clarifai endpoint 
    */
  def color(colorReq: Array[String]): Either[Option[String], ColorResp] = {
    if (colorReq.length < 1 ) {
      return Left(Some("Needs at least one url"))
    }

    /** Converts the user input into string format. */
    var data = ""
    for (str <- colorReq) { data = data.concat("url=" + str + "&") }
    data = data.dropRight(1) // Removes the last "&" character.

    /** Sends the HTTP request. */
    val response = _commonHTTPRequest(Some(data), "color", "POST", false)
    
    /** Returns the HTTP response into JSON-like class structure. */
    response match {
      case Left(err) => Left(err)
      case Right(result) => {
        val rmap = JSON.parseFull(result).get.asInstanceOf[Map[String, Any]]
        val results = rmap.get("results").get.asInstanceOf[List[Map[String, Any]]]
        var resultsArr = List[ColorResults]()
        
        results.foreach((itemR: Map[String, Any]) => {
          val colors = itemR.get("colors").get.asInstanceOf[List[Map[String, Any]]]
          var colorsArr = List[ResultsColors]()

          colors.foreach((itemC: Map[String, Any]) => {
            val w3c_color = itemC.get("w3c").get.asInstanceOf[Map[String, Any]]
            val rColor:ResultsColors = ResultsColors(
              Colorw3c(
                w3c_color.get("hex").get.asInstanceOf[String],
                w3c_color.get("name").get.asInstanceOf[String]
              ),
              itemC.get("hex").get.asInstanceOf[String],
              itemC.get("density").get.asInstanceOf[Double]
            )
            colorsArr :::= List(rColor)
          })

          val cResult:ColorResults = ColorResults(
            itemR.get("docid").get.asInstanceOf[Double],
            itemR.get("url").get.asInstanceOf[String],
            itemR.get("docid_str").get.asInstanceOf[String],
            colorsArr
          )
          resultsArr :::= List(cResult)
        })

        Right(
          ColorResp(
            rmap.get("status_code").get.asInstanceOf[String],
            rmap.get("status_msg").get.asInstanceOf[String],
            resultsArr
          )
        )
      }
    }
  }

  /** Info - returns the current API details as well as any usage limits your account has.
    * 
    * @return Info response from the Clarifai endpoint 
    */
  def info(): Either[Option[String], InfoResp] = {
    /** Sends the HTTP request. */
    val response = _commonHTTPRequest(None, "info", "GET", false)
    
    /** Returns the HTTP response into JSON-like class structure. */
    response match {
      case Left(err) => Left(err)
      case Right(result) => {
        val rmap = JSON.parseFull(result).get.asInstanceOf[Map[String, Any]]
        val results = rmap.get("results").get.asInstanceOf[Map[String, Any]]
        
        Right(
          InfoResp(
            rmap.get("status_code").get.asInstanceOf[String],
            rmap.get("status_msg").get.asInstanceOf[String],
            InfoResults(
              results.get("max_image_size").get.asInstanceOf[Double],
              results.get("default_language").get.asInstanceOf[String],
              results.get("max_video_size").get.asInstanceOf[Double],
              results.get("max_image_bytes").get.asInstanceOf[Double],
              results.get("min_image_size").get.asInstanceOf[Double],
              results.get("default_model").get.asInstanceOf[String],
              results.get("max_video_bytes").get.asInstanceOf[Double],
              results.get("max_video_duration").get.asInstanceOf[Double],
              results.get("max_batch_size").get.asInstanceOf[Double],
              results.get("max_video_batch_size").get.asInstanceOf[Double],
              results.get("min_video_size").get.asInstanceOf[Double],
              results.get("api_version").get.asInstanceOf[Double]
            )
          )
        )
      }
    }
  }

  /** Usage - returns your API usage for the current month and hour.
    * 
    * @return Usage response from the Clarifai endpoint 
    */
  def usage(): Either[Option[String], UsageResp] = {
    /** Sends the HTTP request. */
    val response = _commonHTTPRequest(None, "usage", "GET", false)
    
    /** Returns the HTTP response into JSON-like class structure. */
    response match {
      case Left(err) => Left(err)
      case Right(result) => {
        val rmap = JSON.parseFull(result).get.asInstanceOf[Map[String, Any]]
        val results = rmap.get("results").get.asInstanceOf[Map[String, Any]]
        
        var utArr = List[UsageResultUT]()
        val uThrottles = results.get("user_throttles").get.asInstanceOf[List[Map[String, Any]]]
        uThrottles.foreach((item: Map[String, Any]) => {
          val uThrottle:UsageResultUT = UsageResultUT(
            item.get("name").get.asInstanceOf[String],
            item.get("consumed").get.asInstanceOf[Double],
            item.get("consumed_percentage").get.asInstanceOf[Double],
            item.get("limit").get.asInstanceOf[Double],
            item.get("units").get.asInstanceOf[String],
            item.get("wait").get.asInstanceOf[Double]
          )
          utArr :::= List(uThrottle)
        })

        Right(
          UsageResp(
            rmap.get("status_code").get.asInstanceOf[String],
            rmap.get("status_msg").get.asInstanceOf[String],
            UsageResults(
              utArr,
              results.get("app_throttles").get.asInstanceOf[Map[String, Any]]
            )
          )
        )
      }
    }
  }

  /** Tag - tags the contents of your images or videos.
    * 
    * @param Map of values containing images and videos for tagging
    * @return Tag response from the Clarifai endpoint 
    */
  def tag(tagReq: Map[String, Any]): Either[Option[String], TagResp] = {
    if (!tagReq.contains("url") || tagReq.get("url").get.asInstanceOf[Array[String]].length < 1 ) {
      return Left(Some("Needs at least one url"))
    }

    /** Converts the user input into string format. */
    var data = ""
    data = data.concat(_extractStringWithAmp(tagReq, "model"))
    data = data.concat(_extractStringWithAmp(tagReq, "language"))
    /** TODO: select classes. */
    for (str <- tagReq.get("url").get.asInstanceOf[Array[String]]) {
      data = data.concat("url=" + str + "&")
    }
    data = data.dropRight(1) // Removes the last "&" character.
  
    val response = _commonHTTPRequest(Some(data), "tag", "POST", false)
    
    /** Returns the HTTP response into JSON-like class structure. */
    response match {
      case Left(err) => Left(err)
      case Right(result) => {
        val rmap = JSON.parseFull(result).get.asInstanceOf[Map[String, Any]]
        val meta = rmap.get("meta").get.asInstanceOf[Map[String, Any]]
        val meta_tag = meta.get("tag").get.asInstanceOf[Map[String, Any]]
        val results = rmap.get("results").get.asInstanceOf[List[Map[String, Any]]]
        var resultsArr = List[TagResult]()
        
        results.foreach((item: Map[String, Any]) => {
          val res = item.get("result").get.asInstanceOf[Map[String, Any]]
          val res_tag = res.get("tag").get.asInstanceOf[Map[String, Any]]

          val tResult:TagResult = TagResult(
            item.get("docid").get.asInstanceOf[Double],
            item.get("url").get.asInstanceOf[String],
            item.get("status_code").get.asInstanceOf[String],
            item.get("status_msg").get.asInstanceOf[String],
            item.get("local_id").get.asInstanceOf[String],
            TagResultRes(
              TagResultResTag(
                // res_tag.get("concept_ids").get.asInstanceOf[List[String]],
                res_tag.get("classes").get.asInstanceOf[List[String]],
                res_tag.get("probs").get.asInstanceOf[List[Double]]
              )
            ),
            item.get("docid_str").get.asInstanceOf[String]
          )

          resultsArr :::= List(tResult)
        })

        Right(
          TagResp(
            rmap.get("status_code").get.asInstanceOf[String],
            rmap.get("status_msg").get.asInstanceOf[String],
            TagMeta(
              TagMetaTag(
                meta_tag.get("timestamp").get.asInstanceOf[Double],
                meta_tag.get("model").get.asInstanceOf[String],
                meta_tag.get("config").get.asInstanceOf[String]
              )
            ),
            resultsArr
          )
        )
      }
    }
  }

  /** Functions for establishing the underlying conneciton with the Clarifai API service.
    * 
    * The following functions should be private. They help to establish HTTP connection 
    * and handle the user input and response error.
    */

  /** Requests access token from the Clarifai API service. */
  private def _requestAccessToken(): Option[String]  = {
    val form = Seq("grant_type" -> "client_credentials", 
                    "client_id" -> clientID,
                    "client_secret" -> clientSecret)

    val url = _buildURL("token")
    val response: HttpResponse[String] = Http(url).postForm(form)
                        .header("Authorization", ("Bearer " + accessToken))
                        .header("content-type", "application/x-www-form-urlencoded")
                        .asString
    
    if (response.isError) return Some("4XX OR 5XX ERROR")

    val json_body:Map[String,Any] = JSON.parseFull(response.body).get.asInstanceOf[Map[String, Any]]
    if (json_body.isEmpty) return Some("EMPTY_JSON")
    
    val token = json_body.get("access_token").get.asInstanceOf[String]
    if (token == "") return Some("EMPTY_TOKEN")

    _setAccessToken(token)
    None
  }

  /** Sends HTTP request to the specified enpoint with the user input data. */
  private def _commonHTTPRequest(data:Option[String] ,endpoint: String, verb:String, retry: Boolean)
        : Either[Option[String], String] = {
    val req_data = data match {
      case Some(i) => i
      case None => ""
    }

    val url = _buildURL(endpoint)
    var response: HttpResponse[String] = null
    /** Sends HTTP request based on the method type. */
    verb match {
      case "POST" => {
        response = Http(url).postData(req_data)
                        .header("Authorization", ("Bearer " + accessToken))
                        .header("content-type", "application/x-www-form-urlencoded")
                        .asString
      }
      case "GET" => {
        response = Http(url).header("Authorization", ("Bearer " + accessToken)).asString
      }
      case _ => {
        return Left(Some("ILLEGAL_VERB"))
      }
    }

    /** Matches HTTP response code to the corresponding return value. */
    response.code match {
      case 200|201 => {
        if (throttled) {
          _setThrottle(false)
        }
        Right(response.body)
      }
      case 401 => {
        if (!retry) {
          val err = _requestAccessToken()
          if (err != None) {
            return Left(err)
          }
          return _commonHTTPRequest(data, endpoint, verb, true)
        }
        Left(Some("TOKEN_INVALID"))
      }
      case 429 => {
        _setThrottle(true)
        Left(Some("THROTTLED"))
      }
      case 400 => {
        Left(Some("ALL_ERROR"))
      }
      case 500 => {
        Left(Some("CLARIFAI_ERROR"))
      }
      case _ => {
        Left(Some("UNEXPECTED_STATUS_CODE"))
      }
    }
  }

  /** Helper functions for modifying request and response data. */
  def _buildURL(endpoint: String): String = {
    val parts = Array(Config.baseURL, Config.version, endpoint)
    return parts.mkString("/")
  }

  def _setAccessToken(token: String) = {
    accessToken = token
  }

  def _setThrottle(throttle:Boolean) = {
    throttled = throttle
  }

  def _extractStringWithAmp(obj: Map[String, Any], field: String): String = {
    if (obj.contains(field)) {
      field + "=" + obj.get(field).get.asInstanceOf[String] + "&"
    }
    else {
      ""
    }
  }

  def _extractStrArrWithAmp(obj: Map[String, Any], field: String): String= {
    if (obj.contains(field)) {
      var data = field + "="
      for (str <- obj.get(field).get.asInstanceOf[Array[String]]) {
        data = data.concat(str + ",")
      }
      data = data.dropRight(1) // Removes the last "," character.
      data + "&"
    }
    else {
      ""
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy