JsonUtil.java

/*
 * Copyright 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.media3.transformer;

import android.os.Build;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
import com.google.common.collect.ImmutableList;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/** Utilities for working with JSON */
@UnstableApi
public final class JsonUtil {

  private JsonUtil() {}

  /**
   * Returns a {@link JSONObject} containing device specific details from {@link Build}, including
   * manufacturer, model, SDK version and build fingerprint.
   */
  public static JSONObject getDeviceDetailsAsJsonObject() throws JSONException {
    return new JSONObject()
        .put("manufacturer", Build.MANUFACTURER)
        .put("model", Build.MODEL)
        .put("sdkVersion", Build.VERSION.SDK_INT)
        .put("fingerprint", Build.FINGERPRINT);
  }

  /**
   * Creates a {@link JSONArray} from {@link ExportResult.ProcessedInput processed inputs}.
   *
   * @param processedInputs The list of {@link ExportResult.ProcessedInput} instances.
   * @return A {@link JSONArray} containing {@link JSONObject} instances representing the {@link
   *     ExportResult.ProcessedInput} instances.
   * @throws JSONException if this method attempts to create a JSON with null key.
   */
  public static JSONArray processedInputsAsJsonArray(
      ImmutableList<ExportResult.ProcessedInput> processedInputs) throws JSONException {
    JSONArray jsonArray = new JSONArray();
    for (ExportResult.ProcessedInput processedInput : processedInputs) {
      JSONObject jsonObject = new JSONObject();
      @Nullable
      MediaItem.LocalConfiguration localConfiguration = processedInput.mediaItem.localConfiguration;
      if (localConfiguration != null) {
        jsonObject.put("mediaItemUri", localConfiguration.uri);
      }
      jsonObject.putOpt("audioDecoderName", processedInput.audioDecoderName);
      jsonObject.putOpt("videoDecoderName", processedInput.videoDecoderName);
      jsonArray.put(jsonObject);
    }
    return jsonArray;
  }

  /**
   * Creates a {@link JSONObject} from the {@link Exception}.
   *
   * <p>If the exception is an {@link ExportException}, {@code errorCode} is included.
   *
   * @param exception The {@link Exception}.
   * @return The {@link JSONObject} containing the exception details, or {@code null} if the
   *     exception was {@code null}.
   * @throws JSONException if this method attempts to create a JSON with null key.
   */
  @Nullable
  public static JSONObject exceptionAsJsonObject(@Nullable Exception exception)
      throws JSONException {
    if (exception == null) {
      return null;
    }
    JSONObject exceptionJson = new JSONObject();
    exceptionJson.put("message", exception.getMessage());
    exceptionJson.put("type", exception.getClass());
    if (exception instanceof ExportException) {
      exceptionJson.put("errorCode", ((ExportException) exception).errorCode);
    }
    exceptionJson.put("stackTrace", Log.getThrowableString(exception));
    return exceptionJson;
  }

  /**
   * Creates a {@link JSONObject} from the {@link ExportResult}.
   *
   * @param exportResult The {@link ExportResult}.
   * @return The {@link JSONObject} describing the {@code exportResult}.
   * @throws JSONException if this method attempts to create a JSON with null key.
   */
  public static JSONObject exportResultAsJsonObject(ExportResult exportResult)
      throws JSONException {
    JSONObject jsonObject =
        new JSONObject()
            .putOpt("audioEncoderName", exportResult.audioEncoderName)
            .putOpt("colorInfo", exportResult.colorInfo)
            .putOpt("videoEncoderName", exportResult.videoEncoderName)
            .putOpt("testException", exceptionAsJsonObject(exportResult.exportException));

    if (!exportResult.processedInputs.isEmpty()) {
      jsonObject.put("processedInputs", processedInputsAsJsonArray(exportResult.processedInputs));
    }

    if (exportResult.averageAudioBitrate != C.RATE_UNSET_INT) {
      jsonObject.put("averageAudioBitrate", exportResult.averageAudioBitrate);
    }
    if (exportResult.averageVideoBitrate != C.RATE_UNSET_INT) {
      jsonObject.put("averageVideoBitrate", exportResult.averageVideoBitrate);
    }
    if (exportResult.channelCount != C.LENGTH_UNSET) {
      jsonObject.put("channelCount", exportResult.channelCount);
    }
    if (exportResult.durationMs != C.TIME_UNSET) {
      jsonObject.put("durationMs", exportResult.durationMs);
    }
    if (exportResult.fileSizeBytes != C.LENGTH_UNSET) {
      jsonObject.put("fileSizeBytes", exportResult.fileSizeBytes);
    }
    if (exportResult.height != C.LENGTH_UNSET) {
      jsonObject.put("height", exportResult.height);
    }
    if (exportResult.sampleRate != C.RATE_UNSET_INT) {
      jsonObject.put("sampleRate", exportResult.sampleRate);
    }
    if (exportResult.videoFrameCount > 0) {
      jsonObject.put("videoFrameCount", exportResult.videoFrameCount);
    }
    if (exportResult.width != C.LENGTH_UNSET) {
      jsonObject.put("width", exportResult.width);
    }
    return jsonObject;
  }
}