From da0722dfe030876789d2f9d9b211baf6ba5e8190 Mon Sep 17 00:00:00 2001 From: Antonio Larrosa Date: Tue, 20 Mar 2018 20:12:52 +0100 Subject: [PATCH] Rename from_file to from_file_using_temporary_files and add a new from_file Rename from_file to from_file_using_temporary_files just in case there's any case in which the new from_file doesn't work (I couldn't find any, but just in case, I guess it would be nice to keep it maybe as deprecated). Add a new from_file function that does all the reading on memory with pipes, not using any temporary file, which is faster and doesn't wear down disks for heavy usages. The new from_file function reads the input file and passes it to ffmpeg using a pipe and then reads ffmpeg output using another pipe directly to memory. Since wav files have the file length in the header and ffmpeg can't write it since it's working on a stream, we modify the resulting raw data from ffmpeg before reading it using the standard method. Fixes #237 Might also fix #209 --- pydub/audio_segment.py | 75 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/pydub/audio_segment.py b/pydub/audio_segment.py index cc738df8..af36fab0 100644 --- a/pydub/audio_segment.py +++ b/pydub/audio_segment.py @@ -445,7 +445,7 @@ def from_mono_audiosegments(cls, *mono_segments): ) @classmethod - def from_file(cls, file, format=None, codec=None, parameters=None, **kwargs): + def from_file_using_temporary_files(cls, file, format=None, codec=None, parameters=None, **kwargs): orig_file = file file = _fd_or_path_or_tempfile(file, 'rb', tempfile=False) @@ -544,6 +544,79 @@ def is_format(f): return obj + @classmethod + def from_file(cls, file, format=None, codec=None, parameters=None, **kwargs): + orig_file = file + file = _fd_or_path_or_tempfile(file, 'rb', tempfile=False) + + if format: + format = format.lower() + format = AUDIO_FILE_EXT_ALIASES.get(format, format) + + def is_format(f): + f = f.lower() + if format == f: + return True + if isinstance(orig_file, basestring): + return orig_file.lower().endswith(".{0}".format(f)) + return False + + if is_format("wav"): + try: + return cls._from_safe_wav(file) + except: + file.seek(0) + elif is_format("raw") or is_format("pcm"): + sample_width = kwargs['sample_width'] + frame_rate = kwargs['frame_rate'] + channels = kwargs['channels'] + metadata = { + 'sample_width': sample_width, + 'frame_rate': frame_rate, + 'channels': channels, + 'frame_width': channels * sample_width + } + return cls(data=file.read(), metadata=metadata) + + conversion_command = [cls.converter, + '-y', # always overwrite existing files + ] + + # If format is not defined + # ffmpeg/avconv will detect it automatically + if format: + conversion_command += ["-f", format] + + if codec: + # force audio decoder + conversion_command += ["-acodec", codec] + + conversion_command += [ + "-i", "-", # input_file options (filename last) + "-vn", # Drop any video streams if there are any + "-f", "wav", # output options (filename last) + "-" + ] + + if parameters is not None: + # extend arguments with arbitrary set + conversion_command.extend(parameters) + + log_conversion(conversion_command) + + p = subprocess.Popen(conversion_command, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p_out, p_err = p.communicate(input=file.read()) + + if p.returncode != 0: + raise CouldntDecodeError("Decoding failed. ffmpeg returned error code: {0}\n\nOutput from ffmpeg/avlib:\n\n{1}".format(p.returncode, p_err)) + + p_out = bytearray(p_out) + p_out[4:8] = struct.pack('