mkteagle
mkteagle

Reputation: 579

Cannot get file (image/video) to upload to Google Photos using the Google Photos Api

I have literally tried all I can think of at this point. Been working on this all weekend. I am using NextJs to upload an image or video to Google Photos. The upload token always returns a token. Using the batchCreate call I have been able to upload one file type which is a .mov file. Everything else has failed and even that while returning successful never shows up in the album I specified in google photos, which is an album that my app, webpage, created via the API. Using the same code, uploading a png or jpg or another video file type fails. I have tried creating FormData and appending the file to that and passing that in as the body. Used all sorts of FileReader functions. readAsBinaryString readAsArrayBuffer readAsDataURI none seem to upload the file correctly and the api errors are not worth anything because they are obviously incorrect, they should offer a validateUploadToken call which could be quite useful. Has anyone done this successfully? Here is a spattering of code I am using.

Converter code to convert a file to anything I need it to be

const onDrop = useCallback((acceptedFiles: File[]) => {
    const file = acceptedFiles[0];
    const r = new FileReader();
    let result: Record<string, any> = {};
    r.onload = (e) => {
      onUpdate({
        bytes: e.target?.result ?? "",
        fileName: file.name,
        mimeType: file.type,
      });
    };
    r.readAsBinaryString(file);
}, []);

Here is the api call code for calling to get an upload token.

export async function uploadMediaItemGoogle(
  accessToken: string,
  uploadMediaItem: UploadMediaItem
) {
  const url = "https://photoslibrary.googleapis.com/v1/uploads";
  const response = await fetch(url, {
    headers: {
      Authorization: `Bearer ${accessToken}`,
      "Content-Type": "application/octet-stream",
      "X-Goog-Upload-File-Name": uploadMediaItem.fileName,
      "X-Goog-Upload-Protocol": "raw",
      "X-Goog-Upload-Content-Type": uploadMediaItem.mimeType,
      "X-Goog-Upload-Raw-Size": uploadMediaItem.bytes.length,
    } as any,
    body: uploadMediaItem.bytes,
    method: "POST",
  });
  const uploadResponse = await response.text();
  if (uploadResponse) {
    const createResponse = await createMediaItem(
      accessToken,
      uploadMediaItem,
      uploadResponse
    );
    return createResponse;
  }
}

The batchCreate call to create the media item

export async function createMediaItem(
  accessToken: string,
  uploadMediaItem: UploadMediaItem,
  uploadToken: string
) {
  const url = "https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate";
  const response = await fetch(url, {
    headers: {
      Authorization: `Bearer ${accessToken}`,
      Accept: "application/json",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      albumId: process.env.NEXT_PUBLIC_SHARED_ALBUM_ID!,
      newMediaItems: [
        {
          description: uploadMediaItem.description,
          simpleMediaItem: {
            fileName: uploadMediaItem.fileName,
            uploadToken,
          },
        },
      ],
    }),
    method: "POST",
  });
  const mediaItem = await response.json();
  return mediaItem;
}

The code that actually calls the functions that are calling the api

const [uploadMediaItem, setUploadMediaItem] = useState<UploadMediaItem>({
    description: "",
    fileName: "",
    message: "",
    mimeType: "",
    bytes: null,
  });

async function callApi(token: string) {
    setUploadingMediaItem(true);
    const uploadRes = await uploadMediaItemGoogle(token, uploadMediaItem);
    if (uploadRes) {
      console.log({ uploadRes });
    }
}

  function onUploadMediaItem() {
    if (accessToken && !loadingAccessToken) {
      try {
        callApi(accessToken);
        setUploadProgress(null);
        setUploadingMediaItem(false);
      } catch (error) {
        console.error(error);
      }
    }
  }

If helpful this is what the api is returning after the batch create call

{
    "uploadRes": {
        "newMediaItemResults": [
            {
                "uploadToken": "CAIS6QIAJoFQir42ld+rMy3IIpcNj7DjC3oshob6efr37YBscj... the rest is redacted",
                "status": {
                    "code": 3,
                    "message": "Failed: There was an error while trying to create this media item."
                }
            }
        ]
    }
}

I tried this code and have set the body to each one of these values specified in the keys of this object, but none of them worked either

// none of this code worked, but I gave it a healthy try
    
    export async function returnFileAsBinary(file: File) {
      const toBinString = (bytes: any) =>
        bytes.reduce(
          (str: any, byte: { toString: (arg0: number) => string }) =>
            str + byte.toString(2).padStart(8, "0"),
          ""
        );
      const r = new FileReader();
      const arrayBuffer = await file.arrayBuffer();
      let result: Record<string, any> = {};
      r.onload = (e) => {
        result.arrayBuffer = arrayBuffer;
        result.bufferFrom = Buffer.from(arrayBuffer);
        result.binString = toBinString(new Uint8Array(arrayBuffer));
        result.buffer = Buffer.from(arrayBuffer).toString("binary");
        result.base64Img = e.target?.result as string;
        // result.binaryImg = convertDataURIToBinary(result.base64Img);
        result.blob = new Blob([result.binaryImg], { type: file.type });
        result.blobURL = window.URL.createObjectURL(result.blob);
        result.binaryString = e.target?.result ?? "";
      };
      r.readAsBinaryString(file);
      return result;
    }

Upvotes: 1

Views: 82

Answers (0)

Related Questions