Compare commits
	
		
			17 Commits
		
	
	
		
			2014.12.12
			...
			2014.12.12
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					1c985da0ca | ||
| 
						 | 
					7a60322abf | ||
| 
						 | 
					07bc9a3530 | ||
| 
						 | 
					a099965bad | ||
| 
						 | 
					146323a7f8 | ||
| 
						 | 
					57e086dcea | ||
| 
						 | 
					2101f5d4cc | ||
| 
						 | 
					cc8c9281e6 | ||
| 
						 | 
					cf372f0778 | ||
| 
						 | 
					34bc0ae667 | ||
| 
						 | 
					2865cf0419 | ||
| 
						 | 
					58c1f6f0a7 | ||
| 
						 | 
					7c7a0d395c | ||
| 
						 | 
					8bdcb436f9 | ||
| 
						 | 
					ff815fe65a | ||
| 
						 | 
					00cf122d7a | ||
| 
						 | 
					c7667c2d7f | 
@@ -113,12 +113,12 @@ which means you can modify it, redistribute it or use it however you like.
 | 
			
		||||
                                     size. By default, the buffer size is
 | 
			
		||||
                                     automatically resized from an initial value
 | 
			
		||||
                                     of SIZE.
 | 
			
		||||
    --playlist-reverse               Download playlist videos in reverse order
 | 
			
		||||
 | 
			
		||||
## Filesystem Options:
 | 
			
		||||
    -a, --batch-file FILE            file containing URLs to download ('-' for
 | 
			
		||||
                                     stdin)
 | 
			
		||||
    --id                             use only video ID in file name
 | 
			
		||||
    -A, --auto-number                number downloaded files starting from 00000
 | 
			
		||||
    -o, --output TEMPLATE            output filename template. Use %(title)s to
 | 
			
		||||
                                     get the title, %(uploader)s for the
 | 
			
		||||
                                     uploader name, %(uploader_id)s for the
 | 
			
		||||
@@ -152,6 +152,9 @@ which means you can modify it, redistribute it or use it however you like.
 | 
			
		||||
    --restrict-filenames             Restrict filenames to only ASCII
 | 
			
		||||
                                     characters, and avoid "&" and spaces in
 | 
			
		||||
                                     filenames
 | 
			
		||||
    -A, --auto-number                [deprecated; use  -o
 | 
			
		||||
                                     "%(autonumber)s-%(title)s.%(ext)s" ] number
 | 
			
		||||
                                     downloaded files starting from 00000
 | 
			
		||||
    -t, --title                      [deprecated] use title in file name
 | 
			
		||||
                                     (default)
 | 
			
		||||
    -l, --literal                    [deprecated] alias of --title
 | 
			
		||||
 
 | 
			
		||||
@@ -161,7 +161,9 @@ def assertRegexpMatches(self, text, regexp, msg=None):
 | 
			
		||||
    else:
 | 
			
		||||
        m = re.match(regexp, text)
 | 
			
		||||
        if not m:
 | 
			
		||||
            note = 'Regexp didn\'t match: %r not found in %r' % (regexp, text)
 | 
			
		||||
            note = 'Regexp didn\'t match: %r not found' % (regexp)
 | 
			
		||||
            if len(text) < 1000:
 | 
			
		||||
                note += ' in %r' % text
 | 
			
		||||
            if msg is None:
 | 
			
		||||
                msg = note
 | 
			
		||||
            else:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,11 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
# Allow direct execution
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import unittest
 | 
			
		||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
 | 
			
		||||
 | 
			
		||||
import io
 | 
			
		||||
import os
 | 
			
		||||
import re
 | 
			
		||||
@@ -14,6 +20,9 @@ IGNORED_FILES = [
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from helper import assertRegexpMatches
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestUnicodeLiterals(unittest.TestCase):
 | 
			
		||||
    def test_all_files(self):
 | 
			
		||||
        for dirpath, _, filenames in os.walk(rootDir):
 | 
			
		||||
@@ -29,9 +38,10 @@ class TestUnicodeLiterals(unittest.TestCase):
 | 
			
		||||
 | 
			
		||||
                if "'" not in code and '"' not in code:
 | 
			
		||||
                    continue
 | 
			
		||||
                self.assertRegexpMatches(
 | 
			
		||||
                assertRegexpMatches(
 | 
			
		||||
                    self,
 | 
			
		||||
                    code,
 | 
			
		||||
                    r'(?:#.*\n*)?from __future__ import (?:[a-z_]+,\s*)*unicode_literals',
 | 
			
		||||
                    r'(?:(?:#.*?|\s*)\n)*from __future__ import (?:[a-z_]+,\s*)*unicode_literals',
 | 
			
		||||
                    'unicode_literals import  missing in %s' % fn)
 | 
			
		||||
 | 
			
		||||
                m = re.search(r'(?<=\s)u[\'"](?!\)|,|$)', code)
 | 
			
		||||
 
 | 
			
		||||
@@ -124,6 +124,7 @@ class YoutubeDL(object):
 | 
			
		||||
    nooverwrites:      Prevent overwriting files.
 | 
			
		||||
    playliststart:     Playlist item to start at.
 | 
			
		||||
    playlistend:       Playlist item to end at.
 | 
			
		||||
    playlistreverse:   Download playlist items in reverse order.
 | 
			
		||||
    matchtitle:        Download only matching titles.
 | 
			
		||||
    rejecttitle:       Reject downloads for matching titles.
 | 
			
		||||
    logger:            Log messages to a logging.Logger instance.
 | 
			
		||||
@@ -670,6 +671,9 @@ class YoutubeDL(object):
 | 
			
		||||
                    "[%s] playlist %s: Downloading %d videos" %
 | 
			
		||||
                    (ie_result['extractor'], playlist, n_entries))
 | 
			
		||||
 | 
			
		||||
            if self.params.get('playlistreverse', False):
 | 
			
		||||
                entries = entries[::-1]
 | 
			
		||||
 | 
			
		||||
            for i, entry in enumerate(entries, 1):
 | 
			
		||||
                self.to_screen('[download] Downloading video #%s of %s' % (i, n_entries))
 | 
			
		||||
                extra = {
 | 
			
		||||
 
 | 
			
		||||
@@ -249,6 +249,7 @@ def _real_main(argv=None):
 | 
			
		||||
        'progress_with_newline': opts.progress_with_newline,
 | 
			
		||||
        'playliststart': opts.playliststart,
 | 
			
		||||
        'playlistend': opts.playlistend,
 | 
			
		||||
        'playlistreverse': opts.playlist_reverse,
 | 
			
		||||
        'noplaylist': opts.noplaylist,
 | 
			
		||||
        'logtostderr': opts.outtmpl == '-',
 | 
			
		||||
        'consoletitle': opts.consoletitle,
 | 
			
		||||
 
 | 
			
		||||
@@ -80,6 +80,8 @@ class FileDownloader(object):
 | 
			
		||||
    def calc_eta(start, now, total, current):
 | 
			
		||||
        if total is None:
 | 
			
		||||
            return None
 | 
			
		||||
        if now is None:
 | 
			
		||||
            now = time.time()
 | 
			
		||||
        dif = now - start
 | 
			
		||||
        if current == 0 or dif < 0.001:  # One millisecond
 | 
			
		||||
            return None
 | 
			
		||||
@@ -146,18 +148,19 @@ class FileDownloader(object):
 | 
			
		||||
    def report_error(self, *args, **kargs):
 | 
			
		||||
        self.ydl.report_error(*args, **kargs)
 | 
			
		||||
 | 
			
		||||
    def slow_down(self, start_time, byte_counter):
 | 
			
		||||
    def slow_down(self, start_time, now, byte_counter):
 | 
			
		||||
        """Sleep if the download speed is over the rate limit."""
 | 
			
		||||
        rate_limit = self.params.get('ratelimit', None)
 | 
			
		||||
        if rate_limit is None or byte_counter == 0:
 | 
			
		||||
            return
 | 
			
		||||
        now = time.time()
 | 
			
		||||
        if now is None:
 | 
			
		||||
            now = time.time()
 | 
			
		||||
        elapsed = now - start_time
 | 
			
		||||
        if elapsed <= 0.0:
 | 
			
		||||
            return
 | 
			
		||||
        speed = float(byte_counter) / elapsed
 | 
			
		||||
        if speed > rate_limit:
 | 
			
		||||
            time.sleep((byte_counter - rate_limit * (now - start_time)) / rate_limit)
 | 
			
		||||
            time.sleep(max((byte_counter // rate_limit) - elapsed, 0))
 | 
			
		||||
 | 
			
		||||
    def temp_name(self, filename):
 | 
			
		||||
        """Returns a temporary filename for the given filename."""
 | 
			
		||||
 
 | 
			
		||||
@@ -136,16 +136,21 @@ class HttpFD(FileDownloader):
 | 
			
		||||
        byte_counter = 0 + resume_len
 | 
			
		||||
        block_size = self.params.get('buffersize', 1024)
 | 
			
		||||
        start = time.time()
 | 
			
		||||
 | 
			
		||||
        # measure time over whole while-loop, so slow_down() and best_block_size() work together properly
 | 
			
		||||
        now = None  # needed for slow_down() in the first loop run
 | 
			
		||||
        before = start  # start measuring
 | 
			
		||||
        while True:
 | 
			
		||||
 | 
			
		||||
            # Download and write
 | 
			
		||||
            before = time.time()
 | 
			
		||||
            data_block = data.read(block_size if not is_test else min(block_size, data_len - byte_counter))
 | 
			
		||||
            after = time.time()
 | 
			
		||||
            if len(data_block) == 0:
 | 
			
		||||
                break
 | 
			
		||||
            byte_counter += len(data_block)
 | 
			
		||||
 | 
			
		||||
            # Open file just in time
 | 
			
		||||
            # exit loop when download is finished
 | 
			
		||||
            if len(data_block) == 0:
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
            # Open destination file just in time
 | 
			
		||||
            if stream is None:
 | 
			
		||||
                try:
 | 
			
		||||
                    (stream, tmpfilename) = sanitize_open(tmpfilename, open_mode)
 | 
			
		||||
@@ -161,11 +166,22 @@ class HttpFD(FileDownloader):
 | 
			
		||||
                self.to_stderr('\n')
 | 
			
		||||
                self.report_error('unable to write data: %s' % str(err))
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
            # Apply rate limit
 | 
			
		||||
            self.slow_down(start, now, byte_counter - resume_len)
 | 
			
		||||
 | 
			
		||||
            # end measuring of one loop run
 | 
			
		||||
            now = time.time()
 | 
			
		||||
            after = now
 | 
			
		||||
 | 
			
		||||
            # Adjust block size
 | 
			
		||||
            if not self.params.get('noresizebuffer', False):
 | 
			
		||||
                block_size = self.best_block_size(after - before, len(data_block))
 | 
			
		||||
 | 
			
		||||
            before = after
 | 
			
		||||
 | 
			
		||||
            # Progress message
 | 
			
		||||
            speed = self.calc_speed(start, time.time(), byte_counter - resume_len)
 | 
			
		||||
            speed = self.calc_speed(start, now, byte_counter - resume_len)
 | 
			
		||||
            if data_len is None:
 | 
			
		||||
                eta = percent = None
 | 
			
		||||
            else:
 | 
			
		||||
@@ -186,9 +202,6 @@ class HttpFD(FileDownloader):
 | 
			
		||||
            if is_test and byte_counter == data_len:
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
            # Apply rate limit
 | 
			
		||||
            self.slow_down(start, byte_counter - resume_len)
 | 
			
		||||
 | 
			
		||||
        if stream is None:
 | 
			
		||||
            self.to_stderr('\n')
 | 
			
		||||
            self.report_error('Did not get any data blocks')
 | 
			
		||||
 
 | 
			
		||||
@@ -159,6 +159,7 @@ from .googlesearch import GoogleSearchIE
 | 
			
		||||
from .gorillavid import GorillaVidIE
 | 
			
		||||
from .goshgay import GoshgayIE
 | 
			
		||||
from .grooveshark import GroovesharkIE
 | 
			
		||||
from .groupon import GrouponIE
 | 
			
		||||
from .hark import HarkIE
 | 
			
		||||
from .heise import HeiseIE
 | 
			
		||||
from .helsinki import HelsinkiIE
 | 
			
		||||
 
 | 
			
		||||
@@ -2,12 +2,10 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
from .common import InfoExtractor
 | 
			
		||||
from ..utils import (
 | 
			
		||||
    ExtractorError,
 | 
			
		||||
    int_or_none,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
from .common import InfoExtractor
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -20,8 +18,7 @@ class EbaumsWorldIE(InfoExtractor):
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def _real_extract(self, url):
 | 
			
		||||
        mobj = re.match(self._VALID_URL, url)
 | 
			
		||||
        video_id = mobj.group('id')
 | 
			
		||||
        video_id = self._match_id(url)
 | 
			
		||||
        config = self._download_xml(
 | 
			
		||||
            'http://www.ebaumsworld.com/video/player/%s' % video_id, video_id)
 | 
			
		||||
        video_url = config.find('file').text
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,6 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
from .common import InfoExtractor
 | 
			
		||||
from ..utils import (
 | 
			
		||||
    int_or_none,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GoldenMoustacheIE(InfoExtractor):
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										50
									
								
								youtube_dl/extractor/groupon.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								youtube_dl/extractor/groupon.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
from .common import InfoExtractor
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GrouponIE(InfoExtractor):
 | 
			
		||||
    _VALID_URL = r'https?://www\.groupon\.com/deals/(?P<id>[^?#]+)'
 | 
			
		||||
 | 
			
		||||
    _TEST = {
 | 
			
		||||
        'url': 'https://www.groupon.com/deals/bikram-yoga-huntington-beach-2#ooid=tubGNycTo_9Uxg82uESj4i61EYX8nyuf',
 | 
			
		||||
        'info_dict': {
 | 
			
		||||
            'id': 'bikram-yoga-huntington-beach-2',
 | 
			
		||||
            'title': '$49 for 10 Yoga Classes or One Month of Unlimited Classes at Bikram Yoga Huntington Beach ($180 Value)',
 | 
			
		||||
            'description': 'Studio kept at 105 degrees and 40% humidity with anti-microbial and anti-slip Flotex flooring; certified instructors',
 | 
			
		||||
        },
 | 
			
		||||
        'playlist': [{
 | 
			
		||||
            'info_dict': {
 | 
			
		||||
                'id': 'tubGNycTo_9Uxg82uESj4i61EYX8nyuf',
 | 
			
		||||
                'ext': 'mp4',
 | 
			
		||||
                'title': 'Bikram Yoga Huntington Beach | Orange County',
 | 
			
		||||
            },
 | 
			
		||||
        }],
 | 
			
		||||
        'params': {
 | 
			
		||||
            'skip_download': 'HLS',
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    def _real_extract(self, url):
 | 
			
		||||
        playlist_id = self._match_id(url)
 | 
			
		||||
        webpage = self._download_webpage(url, playlist_id)
 | 
			
		||||
 | 
			
		||||
        payload = self._parse_json(self._search_regex(
 | 
			
		||||
            r'var\s+payload\s*=\s*(.*?);\n', webpage, 'payload'), playlist_id)
 | 
			
		||||
        videos = payload['carousel'].get('dealVideos', [])
 | 
			
		||||
        entries = []
 | 
			
		||||
        for v in videos:
 | 
			
		||||
            if v.get('provider') != 'OOYALA':
 | 
			
		||||
                self.report_warning(
 | 
			
		||||
                    '%s: Unsupported video provider %s, skipping video' %
 | 
			
		||||
                    (playlist_id, v.get('provider')))
 | 
			
		||||
                continue
 | 
			
		||||
            entries.append(self.url_result('ooyala:%s' % v['media']))
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            '_type': 'playlist',
 | 
			
		||||
            'id': playlist_id,
 | 
			
		||||
            'entries': entries,
 | 
			
		||||
            'title': self._og_search_title(webpage),
 | 
			
		||||
            'description': self._og_search_description(webpage),
 | 
			
		||||
        }
 | 
			
		||||
@@ -12,23 +12,37 @@ from ..utils import (
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NBCIE(InfoExtractor):
 | 
			
		||||
    _VALID_URL = r'http://www\.nbc\.com/[^/]+/video/[^/]+/(?P<id>n?\d+)'
 | 
			
		||||
    _VALID_URL = r'http://www\.nbc\.com/(?:[^/]+/)+(?P<id>n?\d+)'
 | 
			
		||||
 | 
			
		||||
    _TEST = {
 | 
			
		||||
        'url': 'http://www.nbc.com/chicago-fire/video/i-am-a-firefighter/2734188',
 | 
			
		||||
        # md5 checksum is not stable
 | 
			
		||||
        'info_dict': {
 | 
			
		||||
            'id': 'bTmnLCvIbaaH',
 | 
			
		||||
            'ext': 'flv',
 | 
			
		||||
            'title': 'I Am a Firefighter',
 | 
			
		||||
            'description': 'An emergency puts Dawson\'sf irefighter skills to the ultimate test in this four-part digital series.',
 | 
			
		||||
    _TESTS = [
 | 
			
		||||
        {
 | 
			
		||||
            'url': 'http://www.nbc.com/chicago-fire/video/i-am-a-firefighter/2734188',
 | 
			
		||||
            # md5 checksum is not stable
 | 
			
		||||
            'info_dict': {
 | 
			
		||||
                'id': 'bTmnLCvIbaaH',
 | 
			
		||||
                'ext': 'flv',
 | 
			
		||||
                'title': 'I Am a Firefighter',
 | 
			
		||||
                'description': 'An emergency puts Dawson\'sf irefighter skills to the ultimate test in this four-part digital series.',
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
        {
 | 
			
		||||
            'url': 'http://www.nbc.com/the-tonight-show/episodes/176',
 | 
			
		||||
            'info_dict': {
 | 
			
		||||
                'id': 'XwU9KZkp98TH',
 | 
			
		||||
                'ext': 'flv',
 | 
			
		||||
                'title': 'Ricky Gervais, Steven Van Zandt, ILoveMakonnen',
 | 
			
		||||
                'description': 'A brand new episode of The Tonight Show welcomes Ricky Gervais, Steven Van Zandt and ILoveMakonnen.',
 | 
			
		||||
            },
 | 
			
		||||
            'skip': 'Only works from US',
 | 
			
		||||
        },
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    def _real_extract(self, url):
 | 
			
		||||
        video_id = self._match_id(url)
 | 
			
		||||
        webpage = self._download_webpage(url, video_id)
 | 
			
		||||
        theplatform_url = self._search_regex('class="video-player video-player-full" data-mpx-url="(.*?)"', webpage, 'theplatform url')
 | 
			
		||||
        theplatform_url = self._search_regex(
 | 
			
		||||
            '(?:class="video-player video-player-full" data-mpx-url|class="player" src)="(.*?)"',
 | 
			
		||||
            webpage, 'theplatform url').replace('_no_endcard', '')
 | 
			
		||||
        if theplatform_url.startswith('//'):
 | 
			
		||||
            theplatform_url = 'http:' + theplatform_url
 | 
			
		||||
        return self.url_result(theplatform_url)
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ class NowVideoIE(NovaMovIE):
 | 
			
		||||
    IE_NAME = 'nowvideo'
 | 
			
		||||
    IE_DESC = 'NowVideo'
 | 
			
		||||
 | 
			
		||||
    _VALID_URL = NovaMovIE._VALID_URL_TEMPLATE % {'host': 'nowvideo\.(?:ch|sx|eu|at|ag|co)'}
 | 
			
		||||
    _VALID_URL = NovaMovIE._VALID_URL_TEMPLATE % {'host': 'nowvideo\.(?:ch|sx|eu|at|ag|co|li)'}
 | 
			
		||||
 | 
			
		||||
    _HOST = 'www.nowvideo.ch'
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -349,6 +349,10 @@ def parseOpts(overrideArguments=None):
 | 
			
		||||
        '--test',
 | 
			
		||||
        action='store_true', dest='test', default=False,
 | 
			
		||||
        help=optparse.SUPPRESS_HELP)
 | 
			
		||||
    downloader.add_option(
 | 
			
		||||
        '--playlist-reverse',
 | 
			
		||||
        action='store_true',
 | 
			
		||||
        help='Download playlist videos in reverse order')
 | 
			
		||||
 | 
			
		||||
    workarounds = optparse.OptionGroup(parser, 'Workarounds')
 | 
			
		||||
    workarounds.add_option(
 | 
			
		||||
@@ -480,10 +484,6 @@ def parseOpts(overrideArguments=None):
 | 
			
		||||
    filesystem.add_option(
 | 
			
		||||
        '--id', default=False,
 | 
			
		||||
        action='store_true', dest='useid', help='use only video ID in file name')
 | 
			
		||||
    filesystem.add_option(
 | 
			
		||||
        '-A', '--auto-number',
 | 
			
		||||
        action='store_true', dest='autonumber', default=False,
 | 
			
		||||
        help='number downloaded files starting from 00000')
 | 
			
		||||
    filesystem.add_option(
 | 
			
		||||
        '-o', '--output',
 | 
			
		||||
        dest='outtmpl', metavar='TEMPLATE',
 | 
			
		||||
@@ -511,6 +511,10 @@ def parseOpts(overrideArguments=None):
 | 
			
		||||
        '--restrict-filenames',
 | 
			
		||||
        action='store_true', dest='restrictfilenames', default=False,
 | 
			
		||||
        help='Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames')
 | 
			
		||||
    filesystem.add_option(
 | 
			
		||||
        '-A', '--auto-number',
 | 
			
		||||
        action='store_true', dest='autonumber', default=False,
 | 
			
		||||
        help='[deprecated; use  -o "%(autonumber)s-%(title)s.%(ext)s" ] number downloaded files starting from 00000')
 | 
			
		||||
    filesystem.add_option(
 | 
			
		||||
        '-t', '--title',
 | 
			
		||||
        action='store_true', dest='usetitle', default=False,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
from __future__ import unicode_literals
 | 
			
		||||
 | 
			
		||||
__version__ = '2014.12.12.3'
 | 
			
		||||
__version__ = '2014.12.12.7'
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user