I'm working on a video to audio converter with react and flask/python. I have received a 500 with this error:
raise IOError(("MoviePy error: the file %s could not be found!\n"
OSError: MoviePy error: the file guitar.mp4 could not be found!
Please check that you entered the correct path.
EDIT: As stated in comments, moviepy VideoFileClip is looking for a path. Per suggestion, I am now attempting to write the incoming video file to a temp directory housed in the backend of the app. The updated stack trace shows the filepath printing, however when presented to VideoFileClip it is still unhappy.
The following snippet is the onSubmit for the video file upload:
const onSubmit = async (e) => {
e.preventDefault()
const data = new FormData()
console.log('hopefully the mp4', videoData)
data.append('mp3', videoData)
console.log('hopefully a form object with mp4', data)
const response = await fetch('/api/convert', {
method: "POST",
body: data
})
if (response.ok) {
const converted = await response.json()
setMp3(converted)
console.log(mp3)
} else {
window.alert("something went wrong :(");
}
}
Here is a link to an image depicting the console output of my file upload from within init.py
app = Flask(__name__)
app.config.from_object(Config)
app.register_blueprint(convert, url_prefix='/api/convert')
CORS(app)
from within converter.py
import os
from flask import Blueprint, jsonify, request
import imageio
from moviepy.editor import *
convert = Blueprint('convert', __name__)
@convert.route('', methods=['POST'])
def convert_mp4():
if request.files['mp3'].filename:
os.getcwd()
filename = request.files['mp3'].filename
print('hey its a file again', filename)
safe_filename = secure_filename(filename)
video_file = os.path.join("/temp/", safe_filename)
print('hey its the file path', video_file)
video_clip = VideoFileClip(video_file)
print('hey its the VideoFileClip', video_clip)
audio_clip = video_clip.audio
audio_clip.write_audiofile(os.path.join("/temp/", f"{safe_filename}-converted.mp3"))
video_clip.close()
audio_clip.close()
return jsonify(send_from_directory(os.path.join("/temp/", f"{safe_filename}-converted.mp3")))
else:
return {'error': 'something went wrong :('}
In the stack trace below you can see file printing the name of the video, my only other thought on why this may not be working was because it was getting lost in the post request, however the fact it is printing after my if file:
check is leaving me pretty confused.
hey its a file again guitar.mp4
hey its the file path /temp/guitar.mp4
127.0.0.1 - - [22/Apr/2021 12:12:15] "POST /api/convert HTTP/1.1" 500 -
Traceback (most recent call last):
File "/home/jasondunn/projects/audioconverter/.venv/lib/python3.8/site-packages/flask/app.py", line 2464, in __call__
return self.wsgi_app(environ, start_response)
File "/home/jasondunn/projects/audioconverter/.venv/lib/python3.8/site-packages/flask/app.py", line 2450, in wsgi_app
response = self.handle_exception(e)
File "/home/jasondunn/projects/audioconverter/.venv/lib/python3.8/site-packages/flask_cors/extension.py", line 161, in wrapped_function
return cors_after_request(app.make_response(f(*args, **kwargs)))
File "/home/jasondunn/projects/audioconverter/.venv/lib/python3.8/site-packages/flask/app.py", line 1867, in handle_exception
reraise(exc_type, exc_value, tb)
File "/home/jasondunn/projects/audioconverter/.venv/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/home/jasondunn/projects/audioconverter/.venv/lib/python3.8/site-packages/flask/app.py", line 2447, in wsgi_app
response = self.full_dispatch_request()
File "/home/jasondunn/projects/audioconverter/.venv/lib/python3.8/site-packages/flask/app.py", line 1952, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/home/jasondunn/projects/audioconverter/.venv/lib/python3.8/site-packages/flask_cors/extension.py", line 161, in wrapped_function
return cors_after_request(app.make_response(f(*args, **kwargs)))
File "/home/jasondunn/projects/audioconverter/.venv/lib/python3.8/site-packages/flask/app.py", line 1821, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/home/jasondunn/projects/audioconverter/.venv/lib/python3.8/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/home/jasondunn/projects/audioconverter/.venv/lib/python3.8/site-packages/flask/app.py", line 1950, in full_dispatch_request
rv = self.dispatch_request()
File "/home/jasondunn/projects/audioconverter/.venv/lib/python3.8/site-packages/flask/app.py", line 1936, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/home/jasondunn/projects/audioconverter/back/api/converter.py", line 20, in convert_mp4
video_clip = VideoFileClip(video_file)
File "/home/jasondunn/projects/audioconverter/.venv/lib/python3.8/site-packages/moviepy/video/io/VideoFileClip.py", line 88, in __init__
self.reader = FFMPEG_VideoReader(filename, pix_fmt=pix_fmt,
File "/home/jasondunn/projects/audioconverter/.venv/lib/python3.8/site-packages/moviepy/video/io/ffmpeg_reader.py", line 35, in __init__
infos = ffmpeg_parse_infos(filename, print_infos, check_duration,
File "/home/jasondunn/projects/audioconverter/.venv/lib/python3.8/site-packages/moviepy/video/io/ffmpeg_reader.py", line 270, in ffmpeg_parse_infos
raise IOError(("MoviePy error: the file %s could not be found!\n"
OSError: MoviePy error: the file /temp/guitar.mp4 could not be found!
Please check that you entered the correct path.
thanks in advance for taking a look/future advice. First official post on Stack Overflow :)
Looks like python cannot find guitar.mp4
:(
I appears that you need to save the file contents on disk before processing. Looking at the docs for MoviePy
you need to pass in the file name or absolute path into VideoFileClip
constructor, this object will open the file on disk and handle processing after instantiation.
Saving the file within the request should be simple enough. The code below should be able to handle this
file.save(os.path.join("/path/to/some/dir", filename))
Now you can give VideoFileClip
a proper URI to the file.
video_clip = VideoFileClip(os.path.join("/path/to/some/dir", filename))
This is what I would write for convert_mp4
although it isn't tested.
@convert.route('', methods=["POST"])
def convert_mp4():
if request.files.get("mp3"):
# clean the filename
safe_filename = secure_filename(request.files["mp3"].filename)
# save file to some directory on disk
request.files["mp3"].save(os.path.join("/path/to/some/dir", safe_filename))
video_clip = VideoFileClip(os.path.join("/path/to/some/dir", safe_filename))
audio_clip = video_clip.audio # convert to audio
# you may need to change the name or save to a different directory
audio_clip.write_audiofile(os.path.join("/path/to/some/dir", f"{safe_filename}.mp3"))
# close resources, maybe use a context manager for better readability
video_clip.close()
audio_clip.close()
# responds with data from a specific file
send_from_directory("/path/to/some/dir", f"{safe_filename}.mp3")
else:
return jsonify(error="File 'mp3' does not exist!")
Whenever you are saving data to disk through flask you should use secure_filename
which is built into flask from the werkzeug project. This function will clean the input name so attackers cannot create malicious filenames.
I would suggest even going a bit further, maybe create 2 endpoints. One for submitting the data to process, and the second for retrieving it. This keeps your requests fast and allows flask to handle other requests in the meantime (however you will need some background process to handle the conversion).
I know we solved this on Discord but I wanted to document the solution.
Your MP4 data is not being saved to disk using the save
method (see this). You can check the above code that implements this.
Once that is done, we now know where this data is and can instantiate the VideoFileClip
object using the known file path, this will allow the conversion to take place and you will need to then save the converted MP3 file on a location within your filesystem.
Once the MP3 is saved to disk, you can use the flask send_from_directory
function to send the data back within your response. This response cannot contain JSON content as the content type is already set to audio/mpeg
based on the MP3 file contents.