ThaDon
ThaDon

Reputation: 8068

Uploading an MP3 using DropZone.js to Google Cloud Storage via signedUrl is corrupted

I upload mp3s to a bucket by creating a "signed upload url" via Google's getSignedUrl call:

const { Storage } = require('@google-cloud/storage');
const bucketName = 'uploads';
const storage = new Storage();
const uploadBucket = storage.bucket(bucketName);
const config = require('config');

const EXPIRES_SECONDS = 600;

exports.generateUploadLink = async (req, res) => {
  let contentId = req.query.contentId || req.body.contentId;

  let file = uploadBucket.file(`${contentId}.mp3`);

  try {
    let signedUrl = await file.getSignedUrl({
      action: 'write',
      expires: Date.now() + (EXPIRES_SECONDS * 1000),
      contentType: 'audio/mpeg'
    });
    res.status(200).json({meta: { status: 'OK'});
  } catch(err) {
    console.log(`Error while getting signed url: ${err}`);
    res.status(500).json({meta: { status: 'FAIL', message: `Could not generate signed upload url for: ${contentId}`, error: err}});
  }
  return;
};

We then use Dropzone to upload it like this:

    $('div#mp3DropzoneArea').dropzone({
      url: uploadUrl,
      previewTemplate: template,
      autoProcessQueue: false,
      createImageThumbnails: false,
      method: 'put',
      clickable: this.get('clickElementSelector'),
      filesizeBase: 1024,
      maxFilesize: 250,  // MB
      acceptedFiles: '.mp3',
      dictDefaultMessage: this.get('defaultMessage'),
      dictInvalidFileType: 'Invalid file type.  Only .mp3 files can be imported.',
      parallelUploads: 1,
      maxFiles: this.get('maxFiles'),
      headers: {
        'Content-Type': 'audio/mpeg'
      },
...
   })

The problem I am facing is that once the file is uploaded, it tends to get corrupted. Now, it will still play, but strange things happen, for instance if I try to seek to 10 seconds in, it plays the file from the beginning and everything is shifted ahead by 10 seconds.

I did a comparison between the source file, and the file after it is uploaded using ffmpeg. Here are the results:

Source MP3

$ ffmpeg -i uncover-iUpa1OtA-20200317.mp3
ffmpeg version 4.2.2 Copyright (c) 2000-2019 the FFmpeg developers
  built with gcc 9.2.1 (GCC) 20200122
  configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt
  libavutil      56. 31.100 / 56. 31.100
  libavcodec     58. 54.100 / 58. 54.100
  libavformat    58. 29.100 / 58. 29.100
  libavdevice    58.  8.100 / 58.  8.100
  libavfilter     7. 57.100 /  7. 57.100
  libswscale      5.  5.100 /  5.  5.100
  libswresample   3.  5.100 /  3.  5.100
  libpostproc    55.  5.100 / 55.  5.100
Input #0, mp3, from 'uncover-iUpa1OtA-20200317.mp3':
  Metadata:
    comment         : From the '60s to the '90s, parents worried messages hidden in rock albums would make their children do drugs and worship the devil. The truth could only be revealed if these records were played backwards. Twenty Thousand Hertz — a podcast about the w
                    :
                    :
    album           : Uncover
    title           : Bonus: Hidden Messages, Backmasking and the Satanic Panic
    artist          : Canadian Broadcasting Corporation
    track           : 1
    date            : 2020
  Duration: 00:29:17.86, start: 0.000000, bitrate: 129 kb/s
    Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 128 kb/s
    Stream #0:1: Video: mjpeg (Progressive), yuvj420p(pc, bt470bg/unknown/unknown), 1400x1400 [SAR 1:1 DAR 1:1], 90k tbr, 90k tbn, 90k tbc (attached pic)
    Metadata:
      comment         : Other

Uploaded MP3

$ ffmpeg -i uncover-uploaded-51ebf5bb-e4ea-4372-b410-d90b04abec6a.mp3
ffmpeg version 4.2.2 Copyright (c) 2000-2019 the FFmpeg developers
  built with gcc 9.2.1 (GCC) 20200122
  configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt
  libavutil      56. 31.100 / 56. 31.100
  libavcodec     58. 54.100 / 58. 54.100
  libavformat    58. 29.100 / 58. 29.100
  libavdevice    58.  8.100 / 58.  8.100
  libavfilter     7. 57.100 /  7. 57.100
  libswscale      5.  5.100 /  5.  5.100
  libswresample   3.  5.100 /  3.  5.100
  libpostproc    55.  5.100 / 55.  5.100
[mp3float @ 0000013aa370d800] Header missing
    Last message repeated 1 times
[mp3 @ 0000013aa370bdc0] Estimating duration from bitrate, this may be inaccurate
Input #0, mp3, from 'uncover-uploaded-51ebf5bb-e4ea-4372-b410-d90b04abec6a.mp3':
  Duration: 00:29:39.25, start: 0.000000, bitrate: 127 kb/s
    Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 128 kb/s
At least one output file must be specified

Notice the "Header missing" and the duration warnings. Gone is all the id3 info from the original.

I inspected the request that Dropzone is making (a PUT) to the uploadUrl and it's of the format:

-----------------------------84271646821941633862998010702
Content-Disposition: form-data; name="file"; filename="uncover-iUpa1OtA-20200317.mp3"
Content-Type: audio/mpeg

<File data...>

Can anyone think of a reason why this may be happening?

Upvotes: 0

Views: 1025

Answers (1)

ThaDon
ThaDon

Reputation: 8068

Ok, figured this out! DropZone was sending a serialized FormData blob that included the MP3 as the body. However, the GCS signedUrl expects only the content of the file.

For instance, when I inspected the content of an uploaded MP3 file, I saw this:

enter image description here

What I did to fix this was to add a new option to the Dropzone instance I was using called gcsUpload. Whenever I'm uploading to GCS, and I am about to set the url for the file, I also set gcsUpload = true.

Inside a sending event handler I setup for Dropzone I do the following:

       dropzone.on('sending', function(file, xhr,/* formData*/) {
          // for Google Cloud Storage we don't want to send formData
          // as that information will be serialized into the file itself.
          // Instead, we only want to send the file to the signedUrl
          if (dropzone.options.gcsUpload) {
            let _send = xhr.send;
            xhr.send = function() {
              _send.call(xhr, file);
            }
          }

        });

Doing this sends only the file data to the signedUrl, not the formdata.

Upvotes: 3

Related Questions