Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added lib folder

  • Loading branch information...
commit 65de7ae2ae428643a7e72bd0d9a6b8f88a44adec 1 parent e863bd5
@Larpon authored
View
44 .gitignore
@@ -11,47 +11,3 @@ __pycache__/
# C extensions
*.so
-
-# Distribution / packaging
-bin/
-build/
-develop-eggs/
-dist/
-eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-*.egg-info/
-.installed.cfg
-*.egg
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-.tox/
-.coverage
-.cache
-nosetests.xml
-coverage.xml
-
-# Translations
-*.mo
-
-# Mr Developer
-.mr.developer.cfg
-.project
-.pydevproject
-
-# Rope
-.ropeproject
-
-# Django stuff:
-*.log
-*.pot
-
-# Sphinx documentation
-docs/_build/
View
1  lib/__init__.py
@@ -0,0 +1 @@
+#!/usr/bin/env python
View
1  lib/pydub/__init__.py
@@ -0,0 +1 @@
+from .audio_segment import AudioSegment
View
683 lib/pydub/audio_segment.py
@@ -0,0 +1,683 @@
+from __future__ import division
+
+import os
+import subprocess
+from tempfile import TemporaryFile, NamedTemporaryFile
+import wave
+import audioop
+import sys
+
+try:
+ from StringIO import StringIO
+except:
+ from io import StringIO, BytesIO
+
+from .utils import (
+ _fd_or_path_or_tempfile,
+ db_to_float,
+ ratio_to_db,
+ get_encoder_name,
+)
+from .exceptions import (
+ TooManyMissingFrames,
+ InvalidDuration,
+ InvalidID3TagVersion,
+ InvalidTag,
+)
+
+if sys.version_info >= (3, 0):
+ basestring = str
+ xrange = range
+ StringIO = BytesIO
+
+AUDIO_FILE_EXT_ALIASES = {
+ "m4a": "mp4"
+}
+
+
+class AudioSegment(object):
+
+ """
+ Note: AudioSegment objects are immutable
+
+ slicable using milliseconds. for example: Get the first second of an mp3...
+
+ a = AudioSegment.from_mp3(mp3file)
+ first_second = a[:1000]
+ """
+ converter = get_encoder_name() # either ffmpeg or avconv
+
+ # TODO: remove in 1.0 release
+ # maintain backwards compatibility for ffmpeg attr (now called converter)
+ ffmpeg = property(lambda s: s.converter,
+ lambda s, v: setattr(s, 'converter', v))
+
+
+ DEFAULT_CODECS = {
+ "ogg": "libvorbis"
+ }
+
+ def __init__(self, data=None, *args, **kwargs):
+ if kwargs.get('metadata', False):
+ # internal use only
+ self._data = data
+ for attr, val in kwargs.pop('metadata').items():
+ setattr(self, attr, val)
+ else:
+ # normal construction
+ data = data if isinstance(data, basestring) else data.read()
+ raw = wave.open(StringIO(data), 'rb')
+
+ raw.rewind()
+ self.channels = raw.getnchannels()
+ self.sample_width = raw.getsampwidth()
+ self.frame_rate = raw.getframerate()
+ self.frame_width = self.channels * self.sample_width
+
+ raw.rewind()
+ self._data = raw.readframes(float('inf'))
+
+ super(AudioSegment, self).__init__(*args, **kwargs)
+
+ def __len__(self):
+ """
+ returns the length of this audio segment in milliseconds
+ """
+ return round(1000 * (self.frame_count() / self.frame_rate))
+
+ def __eq__(self, other):
+ try:
+ return self._data == other._data
+ except:
+ return False
+
+ def __ne__(self, other):
+ return not (self == other)
+
+ def __iter__(self):
+ return (self[i] for i in xrange(len(self)))
+
+ def __getitem__(self, millisecond):
+ if isinstance(millisecond, slice):
+ start = millisecond.start if millisecond.start is not None else 0
+ end = millisecond.stop if millisecond.stop is not None \
+ else len(self)
+
+ start = min(start, len(self))
+ end = min(end, len(self))
+ else:
+ start = millisecond
+ end = millisecond + 1
+
+ start = self._parse_position(start) * self.frame_width
+ end = self._parse_position(end) * self.frame_width
+ data = self._data[start:end]
+
+ # ensure the output is as long as the requester is expecting
+ expected_length = end - start
+ missing_frames = (expected_length - len(data)) // self.frame_width
+ if missing_frames:
+ if missing_frames > self.frame_count(ms=2):
+ raise TooManyMissingFrames("You should never be filling in "
+ " more than 2 ms with silence here, missing frames: %s" %
+ missing_frames)
+ silence = audioop.mul(data[:self.frame_width],
+ self.sample_width, 0)
+ data += (silence * missing_frames)
+
+ return self._spawn(data)
+
+ def get_sample_slice(self, start_sample=None, end_sample=None):
+ """
+ Get a section of the audio segment by sample index. NOTE: Negative
+ indicies do *not* address samples backword from the end of the audio
+ segment like a python list. This is intentional.
+ """
+ max_val = self.frame_count()
+ def bounded(val, default):
+ if val is None: return default
+ if val < 0: return 0
+ if val > max_val: return max_val
+ return val
+
+ start_i = bounded(start_sample, 0) * self.frame_width
+ end_i = bounded(end_sample, max_val) * self.frame_width
+
+ data = self._data[start_i:end_i]
+ return self._spawn(data)
+
+ def __add__(self, arg):
+ if isinstance(arg, AudioSegment):
+ return self.append(arg, crossfade=0)
+ else:
+ return self.apply_gain(arg)
+
+ def __sub__(self, arg):
+ if isinstance(arg, AudioSegment):
+ raise TypeError("AudioSegment objects can't be subtracted from "
+ "each other")
+ else:
+ return self.apply_gain(-arg)
+
+ def __mul__(self, arg):
+ """
+ If the argument is an AudioSegment, overlay the multiplied audio
+ segment.
+
+
+ If it's a number, just use the string multiply operation to repeat the
+ audio so the following would return an AudioSegment that contains the
+ audio of audio_seg eight times
+
+ audio_seg * 8
+ """
+ if isinstance(arg, AudioSegment):
+ return self.overlay(arg, position=0, loop=True)
+ else:
+ return self._spawn(data=self._data * arg)
+
+ def _spawn(self, data, overrides={}):
+ """
+ Creates a new audio segment using the meta data from the current one
+ and the data passed in. Should be used whenever an AudioSegment is
+ being returned by an operation that alters the current one, since
+ AudioSegment objects are immutable.
+ """
+ # accept lists of data chunks
+ if isinstance(data, list):
+ data = b''.join(data)
+
+ # accept file-like objects
+ if hasattr(data, 'read'):
+ if hasattr(data, 'seek'):
+ data.seek(0)
+ data = data.read()
+
+ metadata = {
+ 'sample_width': self.sample_width,
+ 'frame_rate': self.frame_rate,
+ 'frame_width': self.frame_width,
+ 'channels': self.channels
+ }
+ metadata.update(overrides)
+ return AudioSegment(data=data, metadata=metadata)
+
+ @classmethod
+ def _sync(cls, seg1, seg2):
+ s1_len, s2_len = len(seg1), len(seg2)
+
+ channels = max(seg1.channels, seg2.channels)
+ seg1 = seg1.set_channels(channels)
+ seg2 = seg2.set_channels(channels)
+
+ frame_rate = max(seg1.frame_rate, seg2.frame_rate)
+ seg1 = seg1.set_frame_rate(frame_rate)
+ seg2 = seg2.set_frame_rate(frame_rate)
+
+ sample_width = max(seg1.sample_width, seg2.sample_width)
+ seg1 = seg1.set_sample_width(sample_width)
+ seg2 = seg2.set_sample_width(sample_width)
+
+ assert(len(seg1) == s1_len)
+ assert(len(seg2) == s2_len)
+
+ return seg1, seg2
+
+ def _parse_position(self, val):
+ if val < 0:
+ val = len(self) - abs(val)
+ val = self.frame_count(ms=len(self)) if val == float("inf") else \
+ self.frame_count(ms=val)
+ return int(val)
+
+ @classmethod
+ def empty(cls):
+ return cls(b'', metadata={"channels": 1, "sample_width": 1, "frame_rate": 1, "frame_width": 1})
+
+ @classmethod
+ def silent(cls, duration=1000):
+ """
+ Generate a silent audio segment.
+ duration specified in milliseconds (default: 1000ms).
+ """
+ # lowest frame rate I've seen in actual use
+ frame_rate = 11025
+ frames = int(frame_rate * (duration / 1000.0))
+ data = b"\0\0" * frames
+ return cls(data, metadata={"channels": 1,
+ "sample_width": 2,
+ "frame_rate": frame_rate,
+ "frame_width": 2})
+
+ @classmethod
+ def from_file(cls, file, format=None):
+ file = _fd_or_path_or_tempfile(file, 'rb', tempfile=False)
+
+ if format:
+ format = AUDIO_FILE_EXT_ALIASES.get(format, format)
+
+ if format == 'wav':
+ return cls.from_wav(file)
+
+ input_file = NamedTemporaryFile(mode='wb', delete=False)
+ input_file.write(file.read())
+ input_file.flush()
+
+ output = NamedTemporaryFile(mode="rb", delete=False)
+
+ convertion_command = [cls.converter,
+ '-y', # always overwrite existing files
+ ]
+
+ # If format is not defined
+ # ffmpeg/avconv will detect it automatically
+ if format:
+ convertion_command += ["-f", format]
+
+ convertion_command += [
+ "-i", input_file.name, # input_file options (filename last)
+ "-vn", # Drop any video streams if there are any
+ "-f", "wav", # output options (filename last)
+ output.name
+ ]
+
+ subprocess.call(convertion_command, stderr=open(os.devnull))
+
+ obj = cls.from_wav(output)
+
+ input_file.close()
+ output.close()
+ os.unlink(input_file.name)
+ os.unlink(output.name)
+
+ return obj
+
+ @classmethod
+ def from_mp3(cls, file):
+ return cls.from_file(file, 'mp3')
+
+ @classmethod
+ def from_flv(cls, file):
+ return cls.from_file(file, 'flv')
+
+ @classmethod
+ def from_ogg(cls, file):
+ return cls.from_file(file, 'ogg')
+
+ @classmethod
+ def from_wav(cls, file):
+ file = _fd_or_path_or_tempfile(file, 'rb', tempfile=False)
+ file.seek(0)
+ return cls(data=file)
+
+ def export(self, out_f=None, format='mp3', codec=None, bitrate=None, parameters=None, tags=None, id3v2_version='4'):
+ """
+ Export an AudioSegment to a file with given options
+
+ out_f (string):
+ Path to destination audio file
+
+ format (string)
+ Format for destination audio file. ('mp3', 'wav', 'ogg' or other ffmpeg/avconv supported files)
+
+ codec (string)
+ Codec used to encoding for the destination.
+
+ bitrate (string)
+ Bitrate used when encoding destination file. (128, 256, 312k...)
+
+ parameters (string)
+ Aditional ffmpeg/avconv parameters
+
+ tags (dict)
+ Set metadata information to destination files usually used as tags. ({title='Song Title', artist='Song Artist'})
+
+ id3v2_version (string)
+ Set ID3v2 version for tags. (default: '4')
+ """
+ id3v2_allowed_versions = ['3', '4']
+
+ out_f = _fd_or_path_or_tempfile(out_f, 'wb+')
+ out_f.seek(0)
+
+ # for wav output we can just write the data directly to out_f
+ if format == "wav":
+ data = out_f
+ else:
+ data = NamedTemporaryFile(mode="wb", delete=False)
+
+ wave_data = wave.open(data, 'wb')
+ wave_data.setnchannels(self.channels)
+ wave_data.setsampwidth(self.sample_width)
+ wave_data.setframerate(self.frame_rate)
+ # For some reason packing the wave header struct with a float in python 2
+ # doesn't throw an exception
+ wave_data.setnframes(int(self.frame_count()))
+ wave_data.writeframesraw(self._data)
+ wave_data.close()
+
+ # for wav files, we're done (wav data is written directly to out_f)
+ if format == 'wav':
+ return out_f
+
+ output = NamedTemporaryFile(mode="w+b", delete=False)
+
+ # build converter command to export
+ convertion_command = [self.converter,
+ '-y', # always overwrite existing files
+ "-f", "wav", "-i", data.name, # input options (filename last)
+ ]
+
+ if codec is None:
+ codec = self.DEFAULT_CODECS.get(format, None)
+
+ if codec is not None:
+ # force audio encoder
+ convertion_command.extend(["-acodec", codec])
+
+ if bitrate is not None:
+ convertion_command.extend(["-b:a", bitrate])
+
+ if parameters is not None:
+ # extend arguments with arbitrary set
+ convertion_command.extend(parameters)
+
+ if tags is not None:
+ if not isinstance(tags, dict):
+ raise InvalidTag("Tags must be a dictionary.")
+ else:
+ # Extend converter command with tags
+ # print(tags)
+ for key, value in tags.items():
+ convertion_command.extend(['-metadata', '{0}={1}'.format(key, value)])
+
+ if format == 'mp3':
+ # set id3v2 tag version
+ if id3v2_version not in id3v2_allowed_versions:
+ raise InvalidID3TagVersion(
+ "id3v2_version not allowed, allowed versions: %s" % id3v2_allowed_versions)
+ convertion_command.extend([
+ "-id3v2_version", id3v2_version
+ ])
+
+ convertion_command.extend([
+ "-f", format, output.name, # output options (filename last)
+ ])
+
+ # read stdin / write stdout
+ subprocess.call(convertion_command,
+ # make converter shut up
+ stderr=open(os.devnull)
+ )
+
+ output.seek(0)
+ out_f.write(output.read())
+
+ data.close()
+ output.close()
+
+ os.unlink(data.name)
+ os.unlink(output.name)
+
+ out_f.seek(0)
+ return out_f
+
+ def get_frame(self, index):
+ frame_start = index * self.frame_width
+ frame_end = frame_start + self.frame_width
+ return self._data[frame_start:frame_end]
+
+ def frame_count(self, ms=None):
+ """
+ returns the number of frames for the given number of milliseconds, or
+ if not specified, the number of frames in the whole AudioSegment
+ """
+ if ms is not None:
+ return ms * (self.frame_rate / 1000.0)
+ else:
+ return float(len(self._data) // self.frame_width)
+
+ def set_sample_width(self, sample_width):
+ if sample_width == self.sample_width:
+ return self
+
+ data = self._data
+
+ if self.sample_width == 1:
+ data = audioop.bias(data, 1, -128)
+
+ if data:
+ data = audioop.lin2lin(data, self.sample_width, sample_width)
+
+ if sample_width == 1:
+ data = audioop.bias(data, 1, 128)
+
+ frame_width = self.channels * sample_width
+ return self._spawn(data, overrides={'sample_width': sample_width,
+ 'frame_width': frame_width})
+
+
+ def set_frame_rate(self, frame_rate):
+ if frame_rate == self.frame_rate:
+ return self
+
+ if self._data:
+ converted, _ = audioop.ratecv(self._data, self.sample_width,
+ self.channels, self.frame_rate,
+ frame_rate, None)
+ else:
+ converted = self._data
+
+ return self._spawn(data=converted,
+ overrides={'frame_rate': frame_rate})
+
+ def set_channels(self, channels):
+ if channels == self.channels:
+ return self
+
+ if channels == 2 and self.channels == 1:
+ fn = audioop.tostereo
+ frame_width = self.frame_width * 2
+ elif channels == 1 and self.channels == 2:
+ fn = audioop.tomono
+ frame_width = self.frame_width // 2
+
+ converted = fn(self._data, self.sample_width, 1, 1)
+
+ return self._spawn(data=converted, overrides={'channels': channels,
+ 'frame_width': frame_width})
+
+ @property
+ def rms(self):
+ if self.sample_width == 1:
+ return self.set_sample_width(2).rms
+ else:
+ return audioop.rms(self._data, self.sample_width)
+
+ @property
+ def dBFS(self):
+ rms = self.rms
+ if not rms:
+ return -float("infinity")
+ return ratio_to_db(self.rms / self.max_possible_amplitude)
+
+ @property
+ def max(self):
+ return audioop.max(self._data, self.sample_width)
+
+ @property
+ def max_possible_amplitude(self):
+ bits = self.sample_width * 8
+ max_possible_val = (2 ** bits)
+
+ # since half is above 0 and half is below the max amplitude is divided
+ return max_possible_val / 2
+
+ @property
+ def duration_seconds(self):
+ return self.frame_rate and self.frame_count() / self.frame_rate or 0.0
+
+ def apply_gain(self, volume_change):
+ return self._spawn(data=audioop.mul(self._data, self.sample_width,
+ db_to_float(float(volume_change))))
+
+ def overlay(self, seg, position=0, loop=False):
+ output = TemporaryFile()
+
+ seg1, seg2 = AudioSegment._sync(self, seg)
+ sample_width = seg1.sample_width
+ spawn = seg1._spawn
+
+ output.write(seg1[:position]._data)
+
+ # drop down to the raw data
+ seg1 = seg1[position:]._data
+ seg2 = seg2._data
+ pos = 0
+ seg1_len = len(seg1)
+ seg2_len = len(seg2)
+ while True:
+ remaining = max(0, seg1_len - pos)
+ if seg2_len >= remaining:
+ seg2 = seg2[:remaining]
+ seg2_len = remaining
+ loop = False
+
+ output.write(audioop.add(seg1[pos:pos + seg2_len], seg2,
+ sample_width))
+ pos += seg2_len
+
+ if not loop:
+ break
+
+ output.write(seg1[pos:])
+
+ return spawn(data=output)
+
+ def append(self, seg, crossfade=100):
+ output = TemporaryFile()
+
+ seg1, seg2 = AudioSegment._sync(self, seg)
+
+ if not crossfade:
+ return seg1._spawn(seg1._data + seg2._data)
+
+ xf = seg1[-crossfade:].fade(to_gain=-120, start=0, end=float('inf'))
+ xf *= seg2[:crossfade].fade(from_gain=-120, start=0, end=float('inf'))
+
+ output.write(seg1[:-crossfade]._data)
+ output.write(xf._data)
+ output.write(seg2[crossfade:]._data)
+
+ output.seek(0)
+ return seg1._spawn(data=output)
+
+ def fade(self, to_gain=0, from_gain=0, start=None, end=None,
+ duration=None):
+ """
+ Fade the volume of this audio segment.
+
+ to_gain (float):
+ resulting volume_change in db
+
+ start (int):
+ default = beginning of the segment
+ when in this segment to start fading in milliseconds
+
+ end (int):
+ default = end of the segment
+ when in this segment to start fading in milliseconds
+
+ duration (int):
+ default = until the end of the audio segment
+ the duration of the fade
+ """
+ if None not in [duration, end, start]:
+ raise TypeError('Only two of the three arguments, "start", '
+ '"end", and "duration" may be specified')
+
+ # no fade == the same audio
+ if to_gain == 0 and from_gain == 0:
+ return self
+
+ frames = self.frame_count()
+
+ start = min(len(self), start) if start is not None else None
+ end = min(len(self), end) if end is not None else None
+
+ if start is not None and start < 0:
+ start += len(self)
+ if end is not None and end < 0:
+ end += len(self)
+
+ if duration is not None and duration < 0:
+ raise InvalidDuration("duration must be a positive integer")
+
+ if duration:
+ if start is not None:
+ end = start + duration
+ elif end is not None:
+ start = end - duration
+ else:
+ duration = end - start
+
+ from_power = db_to_float(from_gain)
+
+ output = []
+
+ # original data - up until the crossfade portion, as is
+ before_fade = self[:start]._data
+ if from_gain != 0:
+ before_fade = audioop.mul(before_fade, self.sample_width,
+ from_power)
+ output.append(before_fade)
+
+ gain_delta = db_to_float(to_gain) - from_power
+
+ # fades longer than 100ms can use coarse fading (one gain step per ms),
+ # shorter fades will have audible clicks so they use precise fading (one
+ # gain step per sample)
+ if duration > 100:
+ scale_step = gain_delta / duration
+
+ for i in range(duration):
+ volume_change = from_power + (scale_step * i)
+ chunk = self[start + i]
+ chunk = audioop.mul(chunk._data, self.sample_width, volume_change)
+
+ output.append(chunk)
+ else:
+ start_frame = self.frame_count(ms=start)
+ end_frame = self.frame_count(ms=end)
+ fade_frames = end_frame - start_frame
+ scale_step = gain_delta / fade_frames
+
+ for i in range(int(fade_frames)):
+ volume_change = from_power + (scale_step * i)
+ sample = self.get_frame(int(start_frame + i))
+ sample = audioop.mul(sample, self.sample_width, volume_change)
+
+ output.append(sample)
+
+ # original data after the crossfade portion, at the new volume
+ after_fade = self[end:]._data
+ if to_gain != 0:
+ after_fade = audioop.mul(after_fade, self.sample_width,
+ db_to_float(to_gain))
+ output.append(after_fade)
+
+ return self._spawn(data=output)
+
+ def fade_out(self, duration):
+ return self.fade(to_gain=-120, duration=duration, end=float('inf'))
+
+ def fade_in(self, duration):
+ return self.fade(from_gain=-120, duration=duration, start=0)
+
+ def reverse(self):
+ return self._spawn(
+ data=audioop.reverse(self._data, self.sample_width)
+ )
+
+
+from . import effects
View
178 lib/pydub/effects.py
@@ -0,0 +1,178 @@
+import sys
+from .utils import (
+ db_to_float,
+ ratio_to_db,
+ register_pydub_effect,
+ make_chunks,
+)
+from .exceptions import TooManyMissingFrames
+
+if sys.version_info >= (3, 0):
+ xrange = range
+
+@register_pydub_effect
+def normalize(seg, headroom=0.1):
+ """
+ headroom is how close to the maximum volume to boost the signal up to (specified in dB)
+ """
+ peak_sample_val = seg.max
+ target_peak = seg.max_possible_amplitude * db_to_float(-headroom)
+
+ needed_boost = ratio_to_db(target_peak / peak_sample_val)
+ return seg.apply_gain(needed_boost)
+
+
+@register_pydub_effect
+def speedup(seg, playback_speed=1.5, chunk_size=150, crossfade=25):
+ # we will keep audio in 150ms chunks since one waveform at 20Hz is 50ms long
+ # (20 Hz is the lowest frequency audible to humans)
+
+ # portion of AUDIO TO KEEP. if playback speed is 1.25 we keep 80% (0.8) and
+ # discard 20% (0.2)
+ atk = 1.0 / playback_speed
+
+ if playback_speed < 2.0:
+ # throwing out more than half the audio - keep 50ms chunks
+ ms_to_remove_per_chunk = int(chunk_size * (1 - atk) / atk)
+ else:
+ # throwing out less than half the audio - throw out 50ms chunks
+ ms_to_remove_per_chunk = int(chunk_size)
+ chunk_size = int(atk * chunk_size / (1 - atk))
+
+ # the crossfade cannot be longer than the amount of audio we're removing
+ crossfade = min(crossfade, ms_to_remove_per_chunk - 1)
+
+ # DEBUG
+ #print("chunk: {0}, rm: {1}".format(chunk_size, ms_to_remove_per_chunk))
+
+ chunks = make_chunks(seg, chunk_size + ms_to_remove_per_chunk)
+ if len(chunks) < 2:
+ raise Exception("Could not speed up AudioSegment, it was too short {2:0.2f}s for the current settings:\n{0}ms chunks at {1:0.1f}x speedup".format(
+ chunk_size, playback_speed, seg.duration_seconds))
+
+ # we'll actually truncate a bit less than we calculated to make up for the
+ # crossfade between chunks
+ ms_to_remove_per_chunk -= crossfade
+
+ # we don't want to truncate the last chunk since it is not guaranteed to be
+ # the full chunk length
+ last_chunk = chunks[-1]
+ chunks = [chunk[:-ms_to_remove_per_chunk] for chunk in chunks[:-1]]
+
+ out = chunks[0]
+ for chunk in chunks[1:]:
+ out = out.append(chunk, crossfade=crossfade)
+
+ out += last_chunk
+ return out
+
+@register_pydub_effect
+def strip_silence(seg, silence_len=1000, silence_thresh=-20):
+ silence_thresh = seg.rms * db_to_float(silence_thresh)
+
+ # find silence and add start and end indicies to the to_cut list
+ to_cut = []
+ silence_start = None
+ for i, sample in enumerate(seg):
+ if sample.rms < silence_thresh:
+ if silence_start is None:
+ silence_start = i
+ continue
+
+ if silence_start is None:
+ continue
+
+ if i - silence_start > silence_len:
+ to_cut.append([silence_start, i-1])
+
+ silence_start = None
+
+ # print(to_cut)
+
+ keep_silence = 100
+
+ to_cut.reverse()
+ for cstart, cend in to_cut:
+ if len(seg[cend:]) < keep_silence:
+ seg = seg[:cstart + keep_silence]
+ elif len(seg[:cstart]) < keep_silence:
+ seg = seg[cend-keep_silence:]
+ else:
+ #print(cstart, "-", cend)
+ seg = seg[:cstart+keep_silence].append(seg[cend-keep_silence:], crossfade=keep_silence*2)
+ return seg
+
+@register_pydub_effect
+def compress_dynamic_range(seg, threshold=-20.0, ratio=4.0, attack=5.0, release=50.0):
+ """
+ Keyword Arguments:
+
+ threshold - default: -20.0
+ Threshold in dBFS. default of -20.0 means -20dB relative to the
+ maximum possible volume. 0dBFS is the maximum possible value so
+ all values for this argument sould be negative.
+
+ ratio - default: 4.0
+ Compression ratio. Audio louder than the threshold will be
+ reduced to 1/ratio the volume. A ratio of 4.0 is equivalent to
+ a setting of 4:1 in a pro-audio compressor like the Waves C1.
+
+ attack - default: 5.0
+ Attack in milliseconds. How long it should take for the compressor
+ to kick in once the audio has exceeded the threshold.
+
+ release - default: 50.0
+ Release in milliseconds. How long it should take for the compressor
+ to stop compressing after the audio has falled below the threshold.
+
+
+ For an overview of Dynamic Range Compression, and more detailed explanation
+ of the related terminology, see:
+
+ http://en.wikipedia.org/wiki/Dynamic_range_compression
+ """
+ import audioop
+
+ thresh_rms = seg.max_possible_amplitude * db_to_float(threshold)
+
+ look_frames = int(seg.frame_count(ms=attack))
+ def rms_at(frame_i):
+ return seg.get_sample_slice(frame_i - look_frames, frame_i).rms
+ def db_over_threshold(rms):
+ if rms == 0: return 0.0
+ db = ratio_to_db(rms / thresh_rms)
+ return max(db, 0)
+
+ output = []
+
+ # amount to reduce the volume of the audio by (in dB)
+ attenuation = 0.0
+
+ attack_frames = seg.frame_count(ms=attack)
+ release_frames = seg.frame_count(ms=release)
+ for i in xrange(int(seg.frame_count())):
+ rms_now = rms_at(i)
+
+ # with a ratio of 4.0 this means the volume will exceed the threshold by
+ # 1/4 the amount (of dB) that it would otherwise
+ max_attenuation = (1 - (1.0 / ratio)) * db_over_threshold(rms_now)
+
+ attenuation_inc = max_attenuation / attack_frames
+ attenuation_dec = max_attenuation / release_frames
+
+ if rms_now > thresh_rms and attenuation <= max_attenuation:
+ attenuation += attenuation_inc
+ attenuation = min(attenuation, max_attenuation)
+ else:
+ attenuation -= attenuation_dec
+ attenuation = max(attenuation, 0)
+
+ frame = seg.get_frame(i)
+ if attenuation != 0.0:
+ frame = audioop.mul(frame,
+ seg.sample_width,
+ db_to_float(-attenuation))
+
+ output.append(frame)
+
+ return seg._spawn(data=b''.join(output))
View
16 lib/pydub/exceptions.py
@@ -0,0 +1,16 @@
+
+
+class TooManyMissingFrames(Exception):
+ pass
+
+
+class InvalidDuration(Exception):
+ pass
+
+
+class InvalidTag(Exception):
+ pass
+
+
+class InvalidID3TagVersion(Exception):
+ pass
View
10 lib/pydub/playback.py
@@ -0,0 +1,10 @@
+import subprocess
+from tempfile import NamedTemporaryFile
+from .utils import get_player_name
+
+PLAYER = get_player_name()
+
+def play(audio_segment):
+ with NamedTemporaryFile("w+b", suffix=".wav") as f:
+ audio_segment.export(f.name, "wav")
+ subprocess.call([PLAYER, "-nodisp", "-autoexit", f.name])
View
170 lib/pydub/utils.py
@@ -0,0 +1,170 @@
+from __future__ import division
+
+from math import log, ceil, floor
+import os
+import re
+from subprocess import Popen, PIPE
+import sys
+from tempfile import TemporaryFile
+
+if sys.version_info >= (3, 0):
+ basestring = str
+
+
+def _fd_or_path_or_tempfile(fd, mode='w+b', tempfile=True):
+ if fd is None and tempfile:
+ fd = TemporaryFile(mode=mode)
+
+ if isinstance(fd, basestring):
+ fd = open(fd, mode=mode)
+
+ return fd
+
+
+def db_to_float(db):
+ """
+ Converts the input db to a float, which represents the equivalent
+ ratio in power.
+ """
+ db = float(db)
+ return 10 ** (db / 10)
+
+
+def ratio_to_db(ratio, val2=None):
+ """
+ Converts the input float to db, which represents the equivalent
+ to the ratio in power represented by the multiplier passed in.
+ """
+ ratio = float(ratio)
+
+ # accept 2 values and use the ratio of val1 to val2
+ if val2 is not None:
+ ratio = ratio / val2
+
+ return 10 * log(ratio, 10)
+
+
+def register_pydub_effect(fn, name=None):
+ """
+ decorator for adding pydub effects to the AudioSegment objects.
+
+ example use:
+
+ @register_pydub_effect
+ def normalize(audio_segment):
+ ...
+
+ or you can specify a name:
+
+ @register_pydub_effect("normalize")
+ def normalize_audio_segment(audio_segment):
+ ...
+
+ """
+ if isinstance(fn, basestring):
+ name = fn
+ return lambda fn: register_pydub_effect(fn, name)
+
+ if name is None:
+ name = fn.__name__
+
+ from .audio_segment import AudioSegment
+ setattr(AudioSegment, name, fn)
+ return fn
+
+
+def make_chunks(audio_segment, chunk_length):
+ """
+ Breaks an AudioSegment into chunks that are <chunk_length> milliseconds
+ long.
+
+ if chunk_length is 50 then you'll get a list of 50 millisecond long audio
+ segments back (except the last one, which can be shorter)
+ """
+ number_of_chunks = ceil(len(audio_segment) / float(chunk_length))
+ return [audio_segment[i * chunk_length:(i + 1) * chunk_length]
+ for i in range(int(number_of_chunks))]
+
+
+def which(program):
+ """
+ Mimics behavior of UNIX which command.
+ """
+ envdir_list = os.environ["PATH"].split(os.pathsep)
+
+ for envdir in envdir_list:
+ program_path = os.path.join(envdir, program)
+ if os.path.isfile(program_path) and os.access(program_path, os.X_OK):
+ return program_path
+
+
+def get_encoder_name():
+ """
+ Return enconder default application for system, either avconv or ffmpeg
+ """
+ if which("avconv"):
+ return "avconv"
+ elif which("ffmpeg"):
+ return "ffmpeg"
+ else:
+ # should raise exception
+ return "ffmpeg"
+
+def get_player_name():
+ """
+ Return enconder default application for system, either avconv or ffmpeg
+ """
+ if which("avplay"):
+ return "avplay"
+ elif which("ffplay"):
+ return "ffplay"
+ else:
+ # should raise exception
+ return "ffplay"
+
+
+def get_prober_name():
+ """
+ Return probe application, either avconv or ffmpeg
+ """
+ if which("avprobe"):
+ return "avprobe"
+ elif which("ffprobe"):
+ return "ffprobe"
+ else:
+ # should raise exception
+ return "ffprobe"
+
+
+def mediainfo(filepath):
+ """Return dictionary with media info(codec, duration, size, bitrate...) from filepath
+ """
+
+ from .audio_segment import AudioSegment
+
+ command = "{0} -v quiet -show_format -show_streams {1}".format(
+ get_prober_name(),
+ filepath
+ )
+ output = Popen(command.split(), stdout=PIPE).communicate()[0].decode("utf-8")
+
+ rgx = re.compile(r"(?:(?P<inner_dict>.*?):)?(?P<key>.*?)\=(?P<value>.*?)$")
+ info = {}
+ for line in output.split("\n"):
+ # print(line)
+ mobj = rgx.match(line)
+
+ if mobj:
+ # print(mobj.groups())
+ inner_dict, key, value = mobj.groups()
+
+ if inner_dict:
+ try:
+ info[inner_dict]
+ except KeyError:
+ info[inner_dict] = {}
+ info[inner_dict][key] = value
+ else:
+ info[key] = value
+
+ return info
View
315 lib/yaml/__init__.py
@@ -0,0 +1,315 @@
+
+from error import *
+
+from tokens import *
+from events import *
+from nodes import *
+
+from loader import *
+from dumper import *
+
+__version__ = '3.10'
+
+try:
+ from cyaml import *
+ __with_libyaml__ = True
+except ImportError:
+ __with_libyaml__ = False
+
+def scan(stream, Loader=Loader):
+ """
+ Scan a YAML stream and produce scanning tokens.
+ """
+ loader = Loader(stream)
+ try:
+ while loader.check_token():
+ yield loader.get_token()
+ finally:
+ loader.dispose()
+
+def parse(stream, Loader=Loader):
+ """
+ Parse a YAML stream and produce parsing events.
+ """
+ loader = Loader(stream)
+ try:
+ while loader.check_event():
+ yield loader.get_event()
+ finally:
+ loader.dispose()
+
+def compose(stream, Loader=Loader):
+ """
+ Parse the first YAML document in a stream
+ and produce the corresponding representation tree.
+ """
+ loader = Loader(stream)
+ try:
+ return loader.get_single_node()
+ finally:
+ loader.dispose()
+
+def compose_all(stream, Loader=Loader):
+ """
+ Parse all YAML documents in a stream
+ and produce corresponding representation trees.
+ """
+ loader = Loader(stream)
+ try:
+ while loader.check_node():
+ yield loader.get_node()
+ finally:
+ loader.dispose()
+
+def load(stream, Loader=Loader):
+ """
+ Parse the first YAML document in a stream
+ and produce the corresponding Python object.
+ """
+ loader = Loader(stream)
+ try:
+ return loader.get_single_data()
+ finally:
+ loader.dispose()
+
+def load_all(stream, Loader=Loader):
+ """
+ Parse all YAML documents in a stream
+ and produce corresponding Python objects.
+ """
+ loader = Loader(stream)
+ try:
+ while loader.check_data():
+ yield loader.get_data()
+ finally:
+ loader.dispose()
+
+def safe_load(stream):
+ """
+ Parse the first YAML document in a stream
+ and produce the corresponding Python object.
+ Resolve only basic YAML tags.
+ """
+ return load(stream, SafeLoader)
+
+def safe_load_all(stream):
+ """
+ Parse all YAML documents in a stream
+ and produce corresponding Python objects.
+ Resolve only basic YAML tags.
+ """
+ return load_all(stream, SafeLoader)
+
+def emit(events, stream=None, Dumper=Dumper,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None):
+ """
+ Emit YAML parsing events into a stream.
+ If stream is None, return the produced string instead.
+ """
+ getvalue = None
+ if stream is None:
+ from StringIO import StringIO
+ stream = StringIO()
+ getvalue = stream.getvalue
+ dumper = Dumper(stream, canonical=canonical, indent=indent, width=width,
+ allow_unicode=allow_unicode, line_break=line_break)
+ try:
+ for event in events:
+ dumper.emit(event)
+ finally:
+ dumper.dispose()
+ if getvalue:
+ return getvalue()
+
+def serialize_all(nodes, stream=None, Dumper=Dumper,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None,
+ encoding='utf-8', explicit_start=None, explicit_end=None,
+ version=None, tags=None):
+ """
+ Serialize a sequence of representation trees into a YAML stream.
+ If stream is None, return the produced string instead.
+ """
+ getvalue = None
+ if stream is None:
+ if encoding is None:
+ from StringIO import StringIO
+ else:
+ from cStringIO import StringIO
+ stream = StringIO()
+ getvalue = stream.getvalue
+ dumper = Dumper(stream, canonical=canonical, indent=indent, width=width,
+ allow_unicode=allow_unicode, line_break=line_break,
+ encoding=encoding, version=version, tags=tags,
+ explicit_start=explicit_start, explicit_end=explicit_end)
+ try:
+ dumper.open()
+ for node in nodes:
+ dumper.serialize(node)
+ dumper.close()
+ finally:
+ dumper.dispose()
+ if getvalue:
+ return getvalue()
+
+def serialize(node, stream=None, Dumper=Dumper, **kwds):
+ """
+ Serialize a representation tree into a YAML stream.
+ If stream is None, return the produced string instead.
+ """
+ return serialize_all([node], stream, Dumper=Dumper, **kwds)
+
+def dump_all(documents, stream=None, Dumper=Dumper,
+ default_style=None, default_flow_style=None,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None,
+ encoding='utf-8', explicit_start=None, explicit_end=None,
+ version=None, tags=None):
+ """
+ Serialize a sequence of Python objects into a YAML stream.
+ If stream is None, return the produced string instead.
+ """
+ getvalue = None
+ if stream is None:
+ if encoding is None:
+ from StringIO import StringIO
+ else:
+ from cStringIO import StringIO
+ stream = StringIO()
+ getvalue = stream.getvalue
+ dumper = Dumper(stream, default_style=default_style,
+ default_flow_style=default_flow_style,
+ canonical=canonical, indent=indent, width=width,
+ allow_unicode=allow_unicode, line_break=line_break,
+ encoding=encoding, version=version, tags=tags,
+ explicit_start=explicit_start, explicit_end=explicit_end)
+ try:
+ dumper.open()
+ for data in documents:
+ dumper.represent(data)
+ dumper.close()
+ finally:
+ dumper.dispose()
+ if getvalue:
+ return getvalue()
+
+def dump(data, stream=None, Dumper=Dumper, **kwds):
+ """
+ Serialize a Python object into a YAML stream.
+ If stream is None, return the produced string instead.
+ """
+ return dump_all([data], stream, Dumper=Dumper, **kwds)
+
+def safe_dump_all(documents, stream=None, **kwds):
+ """
+ Serialize a sequence of Python objects into a YAML stream.
+ Produce only basic YAML tags.
+ If stream is None, return the produced string instead.
+ """
+ return dump_all(documents, stream, Dumper=SafeDumper, **kwds)
+
+def safe_dump(data, stream=None, **kwds):
+ """
+ Serialize a Python object into a YAML stream.
+ Produce only basic YAML tags.
+ If stream is None, return the produced string instead.
+ """
+ return dump_all([data], stream, Dumper=SafeDumper, **kwds)
+
+def add_implicit_resolver(tag, regexp, first=None,
+ Loader=Loader, Dumper=Dumper):
+ """
+ Add an implicit scalar detector.
+ If an implicit scalar value matches the given regexp,
+ the corresponding tag is assigned to the scalar.
+ first is a sequence of possible initial characters or None.
+ """
+ Loader.add_implicit_resolver(tag, regexp, first)
+ Dumper.add_implicit_resolver(tag, regexp, first)
+
+def add_path_resolver(tag, path, kind=None, Loader=Loader, Dumper=Dumper):
+ """
+ Add a path based resolver for the given tag.
+ A path is a list of keys that forms a path
+ to a node in the representation tree.
+ Keys can be string values, integers, or None.
+ """
+ Loader.add_path_resolver(tag, path, kind)
+ Dumper.add_path_resolver(tag, path, kind)
+
+def add_constructor(tag, constructor, Loader=Loader):
+ """
+ Add a constructor for the given tag.
+ Constructor is a function that accepts a Loader instance
+ and a node object and produces the corresponding Python object.
+ """
+ Loader.add_constructor(tag, constructor)
+
+def add_multi_constructor(tag_prefix, multi_constructor, Loader=Loader):
+ """
+ Add a multi-constructor for the given tag prefix.
+ Multi-constructor is called for a node if its tag starts with tag_prefix.
+ Multi-constructor accepts a Loader instance, a tag suffix,
+ and a node object and produces the corresponding Python object.
+ """
+ Loader.add_multi_constructor(tag_prefix, multi_constructor)
+
+def add_representer(data_type, representer, Dumper=Dumper):
+ """
+ Add a representer for the given type.
+ Representer is a function accepting a Dumper instance
+ and an instance of the given data type
+ and producing the corresponding representation node.
+ """
+ Dumper.add_representer(data_type, representer)
+
+def add_multi_representer(data_type, multi_representer, Dumper=Dumper):
+ """
+ Add a representer for the given type.
+ Multi-representer is a function accepting a Dumper instance
+ and an instance of the given data type or subtype
+ and producing the corresponding representation node.
+ """
+ Dumper.add_multi_representer(data_type, multi_representer)
+
+class YAMLObjectMetaclass(type):
+ """
+ The metaclass for YAMLObject.
+ """
+ def __init__(cls, name, bases, kwds):
+ super(YAMLObjectMetaclass, cls).__init__(name, bases, kwds)
+ if 'yaml_tag' in kwds and kwds['yaml_tag'] is not None:
+ cls.yaml_loader.add_constructor(cls.yaml_tag, cls.from_yaml)
+ cls.yaml_dumper.add_representer(cls, cls.to_yaml)
+
+class YAMLObject(object):
+ """
+ An object that can dump itself to a YAML stream
+ and load itself from a YAML stream.
+ """
+
+ __metaclass__ = YAMLObjectMetaclass
+ __slots__ = () # no direct instantiation, so allow immutable subclasses
+
+ yaml_loader = Loader
+ yaml_dumper = Dumper
+
+ yaml_tag = None
+ yaml_flow_style = None
+
+ def from_yaml(cls, loader, node):
+ """
+ Convert a representation node to a Python object.
+ """
+ return loader.construct_yaml_object(node, cls)
+ from_yaml = classmethod(from_yaml)
+
+ def to_yaml(cls, dumper, data):
+ """
+ Convert a Python object to a representation node.
+ """
+ return dumper.represent_yaml_object(cls.yaml_tag, data, cls,
+ flow_style=cls.yaml_flow_style)
+ to_yaml = classmethod(to_yaml)
+
View
139 lib/yaml/composer.py
@@ -0,0 +1,139 @@
+
+__all__ = ['Composer', 'ComposerError']
+
+from error import MarkedYAMLError
+from events import *
+from nodes import *
+
+class ComposerError(MarkedYAMLError):
+ pass
+
+class Composer(object):
+
+ def __init__(self):
+ self.anchors = {}
+
+ def check_node(self):
+ # Drop the STREAM-START event.
+ if self.check_event(StreamStartEvent):
+ self.get_event()
+
+ # If there are more documents available?
+ return not self.check_event(StreamEndEvent)
+
+ def get_node(self):
+ # Get the root node of the next document.
+ if not self.check_event(StreamEndEvent):
+ return self.compose_document()
+
+ def get_single_node(self):
+ # Drop the STREAM-START event.
+ self.get_event()
+
+ # Compose a document if the stream is not empty.
+ document = None
+ if not self.check_event(StreamEndEvent):
+ document = self.compose_document()
+
+ # Ensure that the stream contains no more documents.
+ if not self.check_event(StreamEndEvent):
+ event = self.get_event()
+ raise ComposerError("expected a single document in the stream",
+ document.start_mark, "but found another document",
+ event.start_mark)
+
+ # Drop the STREAM-END event.
+ self.get_event()
+
+ return document
+
+ def compose_document(self):
+ # Drop the DOCUMENT-START event.
+ self.get_event()
+
+ # Compose the root node.
+ node = self.compose_node(None, None)
+
+ # Drop the DOCUMENT-END event.
+ self.get_event()
+
+ self.anchors = {}
+ return node
+
+ def compose_node(self, parent, index):
+ if self.check_event(AliasEvent):
+ event = self.get_event()
+ anchor = event.anchor
+ if anchor not in self.anchors:
+ raise ComposerError(None, None, "found undefined alias %r"
+ % anchor.encode('utf-8'), event.start_mark)
+ return self.anchors[anchor]
+ event = self.peek_event()
+ anchor = event.anchor
+ if anchor is not None:
+ if anchor in self.anchors:
+ raise ComposerError("found duplicate anchor %r; first occurence"
+ % anchor.encode('utf-8'), self.anchors[anchor].start_mark,
+ "second occurence", event.start_mark)
+ self.descend_resolver(parent, index)
+ if self.check_event(ScalarEvent):
+ node = self.compose_scalar_node(anchor)
+ elif self.check_event(SequenceStartEvent):
+ node = self.compose_sequence_node(anchor)
+ elif self.check_event(MappingStartEvent):
+ node = self.compose_mapping_node(anchor)
+ self.ascend_resolver()
+ return node
+
+ def compose_scalar_node(self, anchor):
+ event = self.get_event()
+ tag = event.tag
+ if tag is None or tag == u'!':
+ tag = self.resolve(ScalarNode, event.value, event.implicit)
+ node = ScalarNode(tag, event.value,
+ event.start_mark, event.end_mark, style=event.style)
+ if anchor is not None:
+ self.anchors[anchor] = node
+ return node
+
+ def compose_sequence_node(self, anchor):
+ start_event = self.get_event()
+ tag = start_event.tag
+ if tag is None or tag == u'!':
+ tag = self.resolve(SequenceNode, None, start_event.implicit)
+ node = SequenceNode(tag, [],
+ start_event.start_mark, None,
+ flow_style=start_event.flow_style)
+ if anchor is not None:
+ self.anchors[anchor] = node
+ index = 0
+ while not self.check_event(SequenceEndEvent):
+ node.value.append(self.compose_node(node, index))
+ index += 1
+ end_event = self.get_event()
+ node.end_mark = end_event.end_mark
+ return node
+
+ def compose_mapping_node(self, anchor):
+ start_event = self.get_event()
+ tag = start_event.tag
+ if tag is None or tag == u'!':
+ tag = self.resolve(MappingNode, None, start_event.implicit)
+ node = MappingNode(tag, [],
+ start_event.start_mark, None,
+ flow_style=start_event.flow_style)
+ if anchor is not None:
+ self.anchors[anchor] = node
+ while not self.check_event(MappingEndEvent):
+ #key_event = self.peek_event()
+ item_key = self.compose_node(node, None)
+ #if item_key in node.value:
+ # raise ComposerError("while composing a mapping", start_event.start_mark,
+ # "found duplicate key", key_event.start_mark)
+ item_value = self.compose_node(node, item_key)
+ #node.value[item_key] = item_value
+ node.value.append((item_key, item_value))
+ end_event = self.get_event()
+ node.end_mark = end_event.end_mark
+ return node
+
View
675 lib/yaml/constructor.py
@@ -0,0 +1,675 @@
+
+__all__ = ['BaseConstructor', 'SafeConstructor', 'Constructor',
+ 'ConstructorError']
+
+from error import *
+from nodes import *
+
+import datetime
+
+import binascii, re, sys, types
+
+class ConstructorError(MarkedYAMLError):
+ pass
+
+class BaseConstructor(object):
+
+ yaml_constructors = {}
+ yaml_multi_constructors = {}
+
+ def __init__(self):
+ self.constructed_objects = {}
+ self.recursive_objects = {}
+ self.state_generators = []
+ self.deep_construct = False
+
+ def check_data(self):
+ # If there are more documents available?
+ return self.check_node()
+
+ def get_data(self):
+ # Construct and return the next document.
+ if self.check_node():
+ return self.construct_document(self.get_node())
+
+ def get_single_data(self):
+ # Ensure that the stream contains a single document and construct it.
+ node = self.get_single_node()
+ if node is not None:
+ return self.construct_document(node)
+ return None
+
+ def construct_document(self, node):
+ data = self.construct_object(node)
+ while self.state_generators:
+ state_generators = self.state_generators
+ self.state_generators = []
+ for generator in state_generators:
+ for dummy in generator:
+ pass
+ self.constructed_objects = {}
+ self.recursive_objects = {}
+ self.deep_construct = False
+ return data
+
+ def construct_object(self, node, deep=False):
+ if node in self.constructed_objects:
+ return self.constructed_objects[node]
+ if deep:
+ old_deep = self.deep_construct
+ self.deep_construct = True
+ if node in self.recursive_objects:
+ raise ConstructorError(None, None,
+ "found unconstructable recursive node", node.start_mark)
+ self.recursive_objects[node] = None
+ constructor = None
+ tag_suffix = None
+ if node.tag in self.yaml_constructors:
+ constructor = self.yaml_constructors[node.tag]
+ else:
+ for tag_prefix in self.yaml_multi_constructors:
+ if node.tag.startswith(tag_prefix):
+ tag_suffix = node.tag[len(tag_prefix):]
+ constructor = self.yaml_multi_constructors[tag_prefix]
+ break
+ else:
+ if None in self.yaml_multi_constructors:
+ tag_suffix = node.tag
+ constructor = self.yaml_multi_constructors[None]
+ elif None in self.yaml_constructors:
+ constructor = self.yaml_constructors[None]
+ elif isinstance(node, ScalarNode):
+ constructor = self.__class__.construct_scalar
+ elif isinstance(node, SequenceNode):
+ constructor = self.__class__.construct_sequence
+ elif isinstance(node, MappingNode):
+ constructor = self.__class__.construct_mapping
+ if tag_suffix is None:
+ data = constructor(self, node)
+ else:
+ data = constructor(self, tag_suffix, node)
+ if isinstance(data, types.GeneratorType):
+ generator = data
+ data = generator.next()
+ if self.deep_construct:
+ for dummy in generator:
+ pass
+ else:
+ self.state_generators.append(generator)
+ self.constructed_objects[node] = data
+ del self.recursive_objects[node]
+ if deep:
+ self.deep_construct = old_deep
+ return data
+
+ def construct_scalar(self, node):
+ if not isinstance(node, ScalarNode):
+ raise ConstructorError(None, None,
+ "expected a scalar node, but found %s" % node.id,
+ node.start_mark)
+ return node.value
+
+ def construct_sequence(self, node, deep=False):
+ if not isinstance(node, SequenceNode):
+ raise ConstructorError(None, None,
+ "expected a sequence node, but found %s" % node.id,
+ node.start_mark)
+ return [self.construct_object(child, deep=deep)
+ for child in node.value]
+
+ def construct_mapping(self, node, deep=False):
+ if not isinstance(node, MappingNode):
+ raise ConstructorError(None, None,
+ "expected a mapping node, but found %s" % node.id,
+ node.start_mark)
+ mapping = {}
+ for key_node, value_node in node.value:
+ key = self.construct_object(key_node, deep=deep)
+ try:
+ hash(key)
+ except TypeError, exc:
+ raise ConstructorError("while constructing a mapping", node.start_mark,
+ "found unacceptable key (%s)" % exc, key_node.start_mark)
+ value = self.construct_object(value_node, deep=deep)
+ mapping[key] = value
+ return mapping
+
+ def construct_pairs(self, node, deep=False):
+ if not isinstance(node, MappingNode):
+ raise ConstructorError(None, None,
+ "expected a mapping node, but found %s" % node.id,
+ node.start_mark)
+ pairs = []
+ for key_node, value_node in node.value:
+ key = self.construct_object(key_node, deep=deep)
+ value = self.construct_object(value_node, deep=deep)
+ pairs.append((key, value))
+ return pairs
+
+ def add_constructor(cls, tag, constructor):
+ if not 'yaml_constructors' in cls.__dict__:
+ cls.yaml_constructors = cls.yaml_constructors.copy()
+ cls.yaml_constructors[tag] = constructor
+ add_constructor = classmethod(add_constructor)
+
+ def add_multi_constructor(cls, tag_prefix, multi_constructor):
+ if not 'yaml_multi_constructors' in cls.__dict__:
+ cls.yaml_multi_constructors = cls.yaml_multi_constructors.copy()
+ cls.yaml_multi_constructors[tag_prefix] = multi_constructor
+ add_multi_constructor = classmethod(add_multi_constructor)
+
+class SafeConstructor(BaseConstructor):
+
+ def construct_scalar(self, node):
+ if isinstance(node, MappingNode):
+ for key_node, value_node in node.value:
+ if key_node.tag == u'tag:yaml.org,2002:value':
+ return self.construct_scalar(value_node)
+ return BaseConstructor.construct_scalar(self, node)
+
+ def flatten_mapping(self, node):
+ merge = []
+ index = 0
+ while index < len(node.value):
+ key_node, value_node = node.value[index]
+ if key_node.tag == u'tag:yaml.org,2002:merge':
+ del node.value[index]
+ if isinstance(value_node, MappingNode):
+ self.flatten_mapping(value_node)
+ merge.extend(value_node.value)
+ elif isinstance(value_node, SequenceNode):
+ submerge = []
+ for subnode in value_node.value:
+ if not isinstance(subnode, MappingNode):
+ raise ConstructorError("while constructing a mapping",
+ node.start_mark,
+ "expected a mapping for merging, but found %s"
+ % subnode.id, subnode.start_mark)
+ self.flatten_mapping(subnode)
+ submerge.append(subnode.value)
+ submerge.reverse()
+ for value in submerge:
+ merge.extend(value)
+ else:
+ raise ConstructorError("while constructing a mapping", node.start_mark,
+ "expected a mapping or list of mappings for merging, but found %s"
+ % value_node.id, value_node.start_mark)
+ elif key_node.tag == u'tag:yaml.org,2002:value':
+ key_node.tag = u'tag:yaml.org,2002:str'
+ index += 1
+ else:
+ index += 1
+ if merge:
+ node.value = merge + node.value
+
+ def construct_mapping(self, node, deep=False):
+ if isinstance(node, MappingNode):
+ self.flatten_mapping(node)
+ return BaseConstructor.construct_mapping(self, node, deep=deep)
+
+ def construct_yaml_null(self, node):
+ self.construct_scalar(node)
+ return None
+
+ bool_values = {
+ u'yes': True,
+ u'no': False,
+ u'true': True,
+ u'false': False,
+ u'on': True,
+ u'off': False,
+ }
+
+ def construct_yaml_bool(self, node):
+ value = self.construct_scalar(node)
+ return self.bool_values[value.lower()]
+
+ def construct_yaml_int(self, node):
+ value = str(self.construct_scalar(node))
+ value = value.replace('_', '')
+ sign = +1
+ if value[0] == '-':
+ sign = -1
+ if value[0] in '+-':
+ value = value[1:]
+ if value == '0':
+ return 0
+ elif value.startswith('0b'):
+ return sign*int(value[2:], 2)
+ elif value.startswith('0x'):
+ return sign*int(value[2:], 16)
+ elif value[0] == '0':
+ return sign*int(value, 8)
+ elif ':' in value:
+ digits = [int(part) for part in value.split(':')]
+ digits.reverse()
+ base = 1
+ value = 0
+ for digit in digits:
+ value += digit*base
+ base *= 60
+ return sign*value
+ else:
+ return sign*int(value)
+
+ inf_value = 1e300
+ while inf_value != inf_value*inf_value:
+ inf_value *= inf_value
+ nan_value = -inf_value/inf_value # Trying to make a quiet NaN (like C99).
+
+ def construct_yaml_float(self, node):
+ value = str(self.construct_scalar(node))
+ value = value.replace('_', '').lower()
+ sign = +1
+ if value[0] == '-':
+ sign = -1
+ if value[0] in '+-':
+ value = value[1:]
+ if value == '.inf':
+ return sign*self.inf_value
+ elif value == '.nan':
+ return self.nan_value
+ elif ':' in value:
+ digits = [float(part) for part in value.split(':')]
+ digits.reverse()
+ base = 1
+ value = 0.0
+ for digit in digits:
+ value += digit*base
+ base *= 60
+ return sign*value
+ else:
+ return sign*float(value)
+
+ def construct_yaml_binary(self, node):
+ value = self.construct_scalar(node)
+ try:
+ return str(value).decode('base64')
+ except (binascii.Error, UnicodeEncodeError), exc:
+ raise ConstructorError(None, None,
+ "failed to decode base64 data: %s" % exc, node.start_mark)
+
+ timestamp_regexp = re.compile(
+ ur'''^(?P<year>[0-9][0-9][0-9][0-9])
+ -(?P<month>[0-9][0-9]?)
+ -(?P<day>[0-9][0-9]?)
+ (?:(?:[Tt]|[ \t]+)
+ (?P<hour>[0-9][0-9]?)
+ :(?P<minute>[0-9][0-9])
+ :(?P<second>[0-9][0-9])
+ (?:\.(?P<fraction>[0-9]*))?
+ (?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
+ (?::(?P<tz_minute>[0-9][0-9]))?))?)?$''', re.X)
+
+ def construct_yaml_timestamp(self, node):
+ value = self.construct_scalar(node)
+ match = self.timestamp_regexp.match(node.value)
+ values = match.groupdict()
+ year = int(values['year'])
+ month = int(values['month'])
+ day = int(values['day'])
+ if not values['hour']:
+ return datetime.date(year, month, day)
+ hour = int(values['hour'])
+ minute = int(values['minute'])
+ second = int(values['second'])
+ fraction = 0
+ if values['fraction']:
+ fraction = values['fraction'][:6]
+ while len(fraction) < 6:
+ fraction += '0'
+ fraction = int(fraction)
+ delta = None
+ if values['tz_sign']:
+ tz_hour = int(values['tz_hour'])
+ tz_minute = int(values['tz_minute'] or 0)
+ delta = datetime.timedelta(hours=tz_hour, minutes=tz_minute)
+ if values['tz_sign'] == '-':
+ delta = -delta
+ data = datetime.datetime(year, month, day, hour, minute, second, fraction)
+ if delta:
+ data -= delta
+ return data
+
+ def construct_yaml_omap(self, node):
+ # Note: we do not check for duplicate keys, because it's too
+ # CPU-expensive.
+ omap = []
+ yield omap
+ if not isinstance(node, SequenceNode):
+ raise ConstructorError("while constructing an ordered map", node.start_mark,
+ "expected a sequence, but found %s" % node.id, node.start_mark)
+ for subnode in node.value:
+ if not isinstance(subnode, MappingNode):
+ raise ConstructorError("while constructing an ordered map", node.start_mark,
+ "expected a mapping of length 1, but found %s" % subnode.id,
+ subnode.start_mark)
+ if len(subnode.value) != 1:
+ raise ConstructorError("while constructing an ordered map", node.start_mark,
+ "expected a single mapping item, but found %d items" % len(subnode.value),
+ subnode.start_mark)
+ key_node, value_node = subnode.value[0]
+ key = self.construct_object(key_node)
+ value = self.construct_object(value_node)
+ omap.append((key, value))
+
+ def construct_yaml_pairs(self, node):
+ # Note: the same code as `construct_yaml_omap`.
+ pairs = []
+ yield pairs
+ if not isinstance(node, SequenceNode):
+ raise ConstructorError("while constructing pairs", node.start_mark,
+ "expected a sequence, but found %s" % node.id, node.start_mark)
+ for subnode in node.value:
+ if not isinstance(subnode, MappingNode):
+ raise ConstructorError("while constructing pairs", node.start_mark,
+ "expected a mapping of length 1, but found %s" % subnode.id,
+ subnode.start_mark)
+ if len(subnode.value) != 1:
+ raise ConstructorError("while constructing pairs", node.start_mark,
+ "expected a single mapping item, but found %d items" % len(subnode.value),
+ subnode.start_mark)
+ key_node, value_node = subnode.value[0]
+ key = self.construct_object(key_node)
+ value = self.construct_object(value_node)
+ pairs.append((key, value))
+
+ def construct_yaml_set(self, node):
+ data = set()
+ yield data
+ value = self.construct_mapping(node)
+ data.update(value)
+
+ def construct_yaml_str(self, node):
+ value = self.construct_scalar(node)
+ try:
+ return value.encode('ascii')
+ except UnicodeEncodeError:
+ return value
+
+ def construct_yaml_seq(self, node):
+ data = []
+ yield data
+ data.extend(self.construct_sequence(node))
+
+ def construct_yaml_map(self, node):
+ data = {}
+ yield data
+ value = self.construct_mapping(node)
+ data.update(value)
+
+ def construct_yaml_object(self, node, cls):
+ data = cls.__new__(cls)
+ yield data
+ if hasattr(data, '__setstate__'):
+ state = self.construct_mapping(node, deep=True)
+ data.__setstate__(state)
+ else:
+ state = self.construct_mapping(node)
+ data.__dict__.update(state)
+
+ def construct_undefined(self, node):
+ raise ConstructorError(None, None,
+ "could not determine a constructor for the tag %r" % node.tag.encode('utf-8'),
+ node.start_mark)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:null',
+ SafeConstructor.construct_yaml_null)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:bool',
+ SafeConstructor.construct_yaml_bool)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:int',
+ SafeConstructor.construct_yaml_int)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:float',
+ SafeConstructor.construct_yaml_float)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:binary',
+ SafeConstructor.construct_yaml_binary)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:timestamp',
+ SafeConstructor.construct_yaml_timestamp)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:omap',
+ SafeConstructor.construct_yaml_omap)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:pairs',
+ SafeConstructor.construct_yaml_pairs)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:set',
+ SafeConstructor.construct_yaml_set)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:str',
+ SafeConstructor.construct_yaml_str)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:seq',
+ SafeConstructor.construct_yaml_seq)
+
+SafeConstructor.add_constructor(
+ u'tag:yaml.org,2002:map',
+ SafeConstructor.construct_yaml_map)
+
+SafeConstructor.add_constructor(None,
+ SafeConstructor.construct_undefined)
+
+class Constructor(SafeConstructor):
+
+ def construct_python_str(self, node):
+ return self.construct_scalar(node).encode('utf-8')
+
+ def construct_python_unicode(self, node):
+ return self.construct_scalar(node)
+
+ def construct_python_long(self, node):
+ return long(self.construct_yaml_int(node))
+
+ def construct_python_complex(self, node):
+ return complex(self.construct_scalar(node))
+
+ def construct_python_tuple(self, node):
+ return tuple(self.construct_sequence(node))
+
+ def find_python_module(self, name, mark):
+ if not name:
+ raise ConstructorError("while constructing a Python module", mark,
+ "expected non-empty name appended to the tag", mark)
+ try:
+ __import__(name)
+ except ImportError, exc:
+ raise ConstructorError("while constructing a Python module", mark,
+ "cannot find module %r (%s)" % (name.encode('utf-8'), exc), mark)
+ return sys.modules[name]
+
+ def find_python_name(self, name, mark):
+ if not name:
+ raise ConstructorError("while constructing a Python object", mark,
+ "expected non-empty name appended to the tag", mark)
+ if u'.' in name:
+ module_name, object_name = name.rsplit('.', 1)
+ else:
+ module_name = '__builtin__'
+ object_name = name
+ try:
+ __import__(module_name)
+ except ImportError, exc:
+ raise ConstructorError("while constructing a Python object", mark,
+ "cannot find module %r (%s)" % (module_name.encode('utf-8'), exc), mark)
+ module = sys.modules[module_name]
+ if not hasattr(module, object_name):
+ raise ConstructorError("while constructing a Python object", mark,
+ "cannot find %r in the module %r" % (object_name.encode('utf-8'),
+ module.__name__), mark)
+ return getattr(module, object_name)
+
+ def construct_python_name(self, suffix, node):
+ value = self.construct_scalar(node)
+ if value:
+ raise ConstructorError("while constructing a Python name", node.start_mark,
+ "expected the empty value, but found %r" % value.encode('utf-8'),
+ node.start_mark)
+ return self.find_python_name(suffix, node.start_mark)
+
+ def construct_python_module(self, suffix, node):
+ value = self.construct_scalar(node)
+ if value:
+ raise ConstructorError("while constructing a Python module", node.start_mark,
+ "expected the empty value, but found %r" % value.encode('utf-8'),
+ node.start_mark)
+ return self.find_python_module(suffix, node.start_mark)
+
+ class classobj: pass
+
+ def make_python_instance(self, suffix, node,
+ args=None, kwds=None, newobj=False):
+ if not args:
+ args = []
+ if not kwds:
+ kwds = {}
+ cls = self.find_python_name(suffix, node.start_mark)
+ if newobj and isinstance(cls, type(self.classobj)) \
+ and not args and not kwds:
+ instance = self.classobj()
+ instance.__class__ = cls
+ return instance
+ elif newobj and isinstance(cls, type):
+ return cls.__new__(cls, *args, **kwds)
+ else:
+ return cls(*args, **kwds)
+
+ def set_python_instance_state(self, instance, state):
+ if hasattr(instance, '__setstate__'):
+ instance.__setstate__(state)
+ else:
+ slotstate = {}
+ if isinstance(state, tuple) and len(state) == 2:
+ state, slotstate = state
+ if hasattr(instance, '__dict__'):
+ instance.__dict__.update(state)
+ elif state:
+ slotstate.update(state)
+ for key, value in slotstate.items():
+ setattr(object, key, value)
+
+ def construct_python_object(self, suffix, node):
+ # Format:
+ # !!python/object:module.name { ... state ... }
+ instance = self.make_python_instance(suffix, node, newobj=True)
+ yield instance
+ deep = hasattr(instance, '__setstate__')
+ state = self.construct_mapping(node, deep=deep)
+ self.set_python_instance_state(instance, state)
+
+ def construct_python_object_apply(self, suffix, node, newobj=False):
+ # Format:
+ # !!python/object/apply # (or !!python/object/new)
+ # args: [ ... arguments ... ]
+ # kwds: { ... keywords ... }
+ # state: ... state ...
+ # listitems: [ ... listitems ... ]
+ # dictitems: { ... dictitems ... }
+ # or short format:
+ # !!python/object/apply [ ... arguments ... ]
+ # The difference between !!python/object/apply and !!python/object/new
+ # is how an object is created, check make_python_instance for details.
+ if isinstance(node, SequenceNode):
+ args = self.construct_sequence(node, deep=True)
+ kwds = {}
+ state = {}
+ listitems = []
+ dictitems = {}
+ else:
+ value = self.construct_mapping(node, deep=True)
+ args = value.get('args', [])
+ kwds = value.get('kwds', {})
+ state = value.get('state', {})
+ listitems = value.get('listitems', [])
+ dictitems = value.get('dictitems', {})
+ instance = self.make_python_instance(suffix, node, args, kwds, newobj)
+ if state:
+ self.set_python_instance_state(instance, state)
+ if listitems:
+ instance.extend(listitems)
+ if dictitems:
+ for key in dictitems:
+ instance[key] = dictitems[key]
+ return instance
+
+ def construct_python_object_new(self, suffix, node):
+ return self.construct_python_object_apply(suffix, node, newobj=True)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/none',
+ Constructor.construct_yaml_null)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/bool',
+ Constructor.construct_yaml_bool)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/str',
+ Constructor.construct_python_str)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/unicode',
+ Constructor.construct_python_unicode)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/int',
+ Constructor.construct_yaml_int)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/long',
+ Constructor.construct_python_long)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/float',
+ Constructor.construct_yaml_float)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/complex',
+ Constructor.construct_python_complex)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/list',
+ Constructor.construct_yaml_seq)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/tuple',
+ Constructor.construct_python_tuple)
+
+Constructor.add_constructor(
+ u'tag:yaml.org,2002:python/dict',
+ Constructor.construct_yaml_map)
+
+Constructor.add_multi_constructor(
+ u'tag:yaml.org,2002:python/name:',
+ Constructor.construct_python_name)
+
+Constructor.add_multi_constructor(
+ u'tag:yaml.org,2002:python/module:',
+ Constructor.construct_python_module)
+
+Constructor.add_multi_constructor(
+ u'tag:yaml.org,2002:python/object:',
+ Constructor.construct_python_object)
+
+Constructor.add_multi_constructor(
+ u'tag:yaml.org,2002:python/object/apply:',
+ Constructor.construct_python_object_apply)
+
+Constructor.add_multi_constructor(
+ u'tag:yaml.org,2002:python/object/new:',
+ Constructor.construct_python_object_new)
+
View
85 lib/yaml/cyaml.py
@@ -0,0 +1,85 @@
+
+__all__ = ['CBaseLoader', 'CSafeLoader', 'CLoader',
+ 'CBaseDumper', 'CSafeDumper', 'CDumper']
+
+from _yaml import CParser, CEmitter
+
+from constructor import *
+
+from serializer import *
+from representer import *
+
+from resolver import *
+
+class CBaseLoader(CParser, BaseConstructor, BaseResolver):
+
+ def __init__(self, stream):
+ CParser.__init__(self, stream)
+ BaseConstructor.__init__(self)
+ BaseResolver.__init__(self)
+
+class CSafeLoader(CParser, SafeConstructor, Resolver):
+
+ def __init__(self, stream):
+ CParser.__init__(self, stream)
+ SafeConstructor.__init__(self)
+ Resolver.__init__(self)
+
+class CLoader(CParser, Constructor, Resolver):
+
+ def __init__(self, stream):
+ CParser.__init__(self, stream)
+ Constructor.__init__(self)
+ Resolver.__init__(self)
+
+class CBaseDumper(CEmitter, BaseRepresenter, BaseResolver):
+
+ def __init__(self, stream,
+ default_style=None, default_flow_style=None,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None,
+ encoding=None, explicit_start=None, explicit_end=None,
+ version=None, tags=None):
+ CEmitter.__init__(self, stream, canonical=canonical,
+ indent=indent, width=width, encoding=encoding,
+ allow_unicode=allow_unicode, line_break=line_break,
+ explicit_start=explicit_start, explicit_end=explicit_end,
+ version=version, tags=tags)
+ Representer.__init__(self, default_style=default_style,
+ default_flow_style=default_flow_style)
+ Resolver.__init__(self)
+
+class CSafeDumper(CEmitter, SafeRepresenter, Resolver):
+
+ def __init__(self, stream,
+ default_style=None, default_flow_style=None,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None,
+ encoding=None, explicit_start=None, explicit_end=None,
+ version=None, tags=None):
+ CEmitter.__init__(self, stream, canonical=canonical,
+ indent=indent, width=width, encoding=encoding,
+ allow_unicode=allow_unicode, line_break=line_break,
+ explicit_start=explicit_start, explicit_end=explicit_end,
+ version=version, tags=tags)
+ SafeRepresenter.__init__(self, default_style=default_style,
+ default_flow_style=default_flow_style)
+ Resolver.__init__(self)
+
+class CDumper(CEmitter, Serializer, Representer, Resolver):
+
+ def __init__(self, stream,
+ default_style=None, default_flow_style=None,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None,
+ encoding=None, explicit_start=None, explicit_end=None,
+ version=None, tags=None):
+ CEmitter.__init__(self, stream, canonical=canonical,
+ indent=indent, width=width, encoding=encoding,
+ allow_unicode=allow_unicode, line_break=line_break,
+ explicit_start=explicit_start, explicit_end=explicit_end,
+ version=version, tags=tags)
+ Representer.__init__(self, default_style=default_style,
+ default_flow_style=default_flow_style)
+ Resolver.__init__(self)
+
View
62 lib/yaml/dumper.py
@@ -0,0 +1,62 @@
+
+__all__ = ['BaseDumper', 'SafeDumper', 'Dumper']
+
+from emitter import *
+from serializer import *
+from representer import *
+from resolver import *
+
+class BaseDumper(Emitter, Serializer, BaseRepresenter, BaseResolver):
+
+ def __init__(self, stream,
+ default_style=None, default_flow_style=None,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None,
+ encoding=None, explicit_start=None, explicit_end=None,
+ version=None, tags=None):
+ Emitter.__init__(self, stream, canonical=canonical,
+ indent=indent, width=width,
+ allow_unicode=allow_unicode, line_break=line_break)
+ Serializer.__init__(self, encoding=encoding,
+ explicit_start=explicit_start, explicit_end=explicit_end,
+ version=version, tags=tags)
+ Representer.__init__(self, default_style=default_style,
+ default_flow_style=default_flow_style)
+ Resolver.__init__(self)
+
+class SafeDumper(Emitter, Serializer, SafeRepresenter, Resolver):
+
+ def __init__(self, stream,
+ default_style=None, default_flow_style=None,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None,
+ encoding=None, explicit_start=None, explicit_end=None,
+ version=None, tags=None):
+ Emitter.__init__(self, stream, canonical=canonical,
+ indent=indent, width=width,
+ allow_unicode=allow_unicode, line_break=line_break)
+ Serializer.__init__(self, encoding=encoding,
+ explicit_start=explicit_start, explicit_end=explicit_end,
+ version=version, tags=tags)
+ SafeRepresenter.__init__(self, default_style=default_style,
+ default_flow_style=default_flow_style)
+ Resolver.__init__(self)
+
+class Dumper(Emitter, Serializer, Representer, Resolver):
+
+ def __init__(self, stream,
+ default_style=None, default_flow_style=None,
+ canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None,
+ encoding=None, explicit_start=None, explicit_end=None,
+ version=None, tags=None):
+ Emitter.__init__(self, stream, canonical=canonical,
+ indent=indent, width=width,
+ allow_unicode=allow_unicode, line_break=line_break)
+ Serializer.__init__(self, encoding=encoding,
+ explicit_start=explicit_start, explicit_end=explicit_end,
+ version=version, tags=tags)
+ Representer.__init__(self, default_style=default_style,
+ default_flow_style=default_flow_style)
+ Resolver.__init__(self)
+
View
1,140 lib/yaml/emitter.py
@@ -0,0 +1,1140 @@
+
+# Emitter expects events obeying the following grammar:
+# stream ::= STREAM-START document* STREAM-END
+# document ::= DOCUMENT-START node DOCUMENT-END
+# node ::= SCALAR | sequence | mapping
+# sequence ::= SEQUENCE-START node* SEQUENCE-END
+# mapping ::= MAPPING-START (node node)* MAPPING-END
+
+__all__ = ['Emitter', 'EmitterError']
+
+from error import YAMLError
+from events import *
+
+class EmitterError(YAMLError):
+ pass
+
+class ScalarAnalysis(object):
+ def __init__(self, scalar, empty, multiline,
+ allow_flow_plain, allow_block_plain,
+ allow_single_quoted, allow_double_quoted,
+ allow_block):
+ self.scalar = scalar
+ self.empty = empty
+ self.multiline = multiline
+ self.allow_flow_plain = allow_flow_plain
+ self.allow_block_plain = allow_block_plain
+ self.allow_single_quoted = allow_single_quoted
+ self.allow_double_quoted = allow_double_quoted
+ self.allow_block = allow_block
+
+class Emitter(object):
+
+ DEFAULT_TAG_PREFIXES = {
+ u'!' : u'!',
+ u'tag:yaml.org,2002:' : u'!!',
+ }
+
+ def __init__(self, stream, canonical=None, indent=None, width=None,
+ allow_unicode=None, line_break=None):
+
+ # The stream should have the methods `write` and possibly `flush`.
+ self.stream = stream
+
+ # Encoding can be overriden by STREAM-START.
+ self.encoding = None
+
+ # Emitter is a state machine with a stack of states to handle nested
+ # structures.
+ self.states = []
+ self.state = self.expect_stream_start
+
+ # Current event and the event queue.
+ self.events = []
+ self.event = None
+
+ # The current indentation level and the stack of previous indents.
+ self.indents = []
+ self.indent = None
+
+ # Flow level.
+ self.flow_level = 0
+
+ # Contexts.
+ self.root_context = False
+ self.sequence_context = False
+ self.mapping_context = False
+ self.simple_key_context = False
+
+ # Characteristics of the last emitted character:
+ # - current position.
+ # - is it a whitespace?
+ # - is it an indention character
+ # (indentation space, '-', '?', or ':')?
+ self.line = 0
+ self.column = 0
+ self.whitespace = True
+ self.indention = True
+
+ # Whether the document requires an explicit document indicator
+ self.open_ended = False
+
+ # Formatting details.
+ self.canonical = canonical
+ self.allow_unicode = allow_unicode
+ self.best_indent = 2
+ if indent and 1 < indent < 10:
+ self.best_indent = indent
+ self.best_width = 80
+ if width and width > self.best_indent*2:
+ self.best_width = width
+ self.best_line_break = u'\n'
+ if line_break in [u'\r', u'\n', u'\r\n']:
+ self.best_line_break = line_break
+
+ # Tag prefixes.
+ self.tag_prefixes = None
+
+ # Prepared anchor and tag.
+ self.prepared_anchor = None
+ self.prepared_tag = None
+
+ # Scalar analysis and style.
+ self.analysis = None
+ self.style = None
+
+ def dispose(self):
+ # Reset the state attributes (to clear self-references)
+ self.states = []
+ self.state = None
+
+ def emit(self, event):
+ self.events.append(event)
+ while not self.need_more_events():
+ self.event = self.events.pop(0)
+ self.state()
+ self.event = None
+
+ # In some cases, we wait for a few next events before emitting.
+
+ def need_more_events(self):
+ if not self.events:
+ return True
+ event = self.events[0]
+ if isinstance(event, DocumentStartEvent):
+ return self.need_events(1)
+ elif isinstance(event, SequenceStartEvent):
+ return self.need_events(2)
+ elif isinstance(event, MappingStartEvent):
+ return self.need_events(3)
+ else:
+ return False
+
+ def need_events(self, count):
+ level = 0
+ for event in self.events[1:]: