From 3893699c9a2984576b62b6c3f1eb77f34a4cf0ab Mon Sep 17 00:00:00 2001 From: Roman Bazalevsky Date: Fri, 24 Jul 2015 14:26:44 +0300 Subject: [PATCH] WebM transcoding added Startup timeout added for upstart compatibility --- README.md~ | 8 -- plugins/m3u_plugin.py | 2 + plugins/torrenttv_api.py | 173 +++++++++++++++++++++++++++++++++++++++ vlcclient/vlcclient.py | 4 +- vlcclient/vlcmessages.py | 3 + vpconfig.py | 22 +---- vphttp.py | 9 +- 7 files changed, 188 insertions(+), 33 deletions(-) delete mode 100644 README.md~ create mode 100644 plugins/torrenttv_api.py diff --git a/README.md~ b/README.md~ deleted file mode 100644 index 24b72df..0000000 --- a/README.md~ +++ /dev/null @@ -1,8 +0,0 @@ -AceProxy: Ace Stream HTTP Proxy -=============================== -AceProxy allows you to watch [Ace Stream](http://acestream.org/) live streams or BitTorrent files over HTTP. -It's written in Python + gevent and should work on both Linux and Windows (Mac OS should work too, but was not tested) - -Currently it supports Ace Stream Content-ID hashes (PIDs), .acestream files and usual torrent files. - -**For installation, configuration and using info, visit** [Wiki](https://github.com/ValdikSS/aceproxy/wiki) diff --git a/plugins/m3u_plugin.py b/plugins/m3u_plugin.py index cc614f2..2263dfe 100644 --- a/plugins/m3u_plugin.py +++ b/plugins/m3u_plugin.py @@ -30,6 +30,8 @@ class M3u(VPProxyPlugin): if self.splitted_path[1]=='m3u': prefix='get' + elif self.splitted_path[1]=='m3uw': + prefix='webm' elif self.splitted_path[1]=='m3ut': prefix='mp4' else: diff --git a/plugins/torrenttv_api.py b/plugins/torrenttv_api.py new file mode 100644 index 0000000..ce5a86d --- /dev/null +++ b/plugins/torrenttv_api.py @@ -0,0 +1,173 @@ +# coding=utf-8 +""" +Torrent-TV API communication class +Forms requests to API, checks result for errors and returns in desired form (lists or raw data) +""" +__author__ = 'miltador' + +import urllib2 +import socket +import xml.dom.minidom as dom + + +class TorrentTvApiException(Exception): + """ + Exception from Torrent-TV API + """ + pass + + +class TorrentTvApi(object): + CATEGORIES = { + 1: 'Детские', + 2: 'Музыка', + 3: 'Фильмы', + 4: 'Спорт', + 5: 'Общие', + 6: 'Познавательные', + 7: 'Новостные', + 8: 'Развлекательные', + 9: 'Для взрослых', + 10: 'Мужские', + 11: 'Региональные', + 12: 'Религиозные' + } + + @staticmethod + def auth(email, password, raw=False): + """ + User authentication + Returns user session that can be used for API requests + + :param email: user email string + :param password: user password string + :param raw: if True returns unprocessed data + :return: unique session string + """ + + xmlresult = TorrentTvApi._result( + 'v2_auth.php?username=' + email + '&password=' + password + '&application=tsproxy') + if raw: + return xmlresult + res = TorrentTvApi._check(xmlresult) + session = res.getElementsByTagName('session')[0].firstChild.data + return session + + @staticmethod + def translations(session, translation_type, raw=False): + """ + Gets list of translations + Translations are basically TV channels + + :param session: valid user session required + :param translation_type: playlist type, valid values: all|channel|moderation|translation|favourite + :param raw: if True returns unprocessed data + :return: translations list + """ + xmlresult = TorrentTvApi._result( + 'v2_alltranslation.php?session=' + session + '&type=' + translation_type) + if raw: + return xmlresult + res = TorrentTvApi._check(xmlresult) + translationslist = res.getElementsByTagName('channel') + return translationslist + + @staticmethod + def records(session, channel_id, date, raw=False): + """ + Gets list of available record for given channel and date + + :param session: valid user session required + :param channel_id: id of channel in channel list + :param date: format %d-%m-%Y + :param raw: if True returns unprocessed data + :return: records list + """ + xmlresult = TorrentTvApi._result( + 'v2_arc_getrecords.php?session=' + session + '&channel_id=' + channel_id + '&date=' + date) + if raw: + return xmlresult + res = TorrentTvApi._check(xmlresult) + recordslist = res.getElementsByTagName('channel') + return recordslist + + @staticmethod + def archive_channels(session, raw=False): + """ + Gets the channels list for archive + + :param session: valid user session required + :param raw: if True returns unprocessed data + :return: archive channels list + """ + xmlresult = TorrentTvApi._result( + 'v2_arc_getchannels.php?session=' + session) + if raw: + return xmlresult + res = TorrentTvApi._check(xmlresult) + archive_channelslist = res.getElementsByTagName('channel') + return archive_channelslist + + @staticmethod + def stream_source(session, channel_id): + """ + Gets the source for Ace Stream by channel id + + :param session: valid user session required + :param channel_id: id of channel in translations list (see translations() method) + :return: type of stream and source + """ + xmlresult = TorrentTvApi._result( + 'v2_get_stream.php?session=' + session + '&channel_id=' + channel_id) + res = TorrentTvApi._check(xmlresult) + stream_type = res.getElementsByTagName('type')[0].firstChild.data + source = res.getElementsByTagName('source')[0].firstChild.data + return stream_type.encode('utf-8'), source.encode('utf-8') + + @staticmethod + def archive_stream_source(session, record_id): + """ + Gets stream source for archive record + + :param session: valid user session required + :param record_id: id of record in records list (see records() method) + :return: type of stream and source + """ + xmlresult = TorrentTvApi._result( + 'v2_arc_getstream.php?session=' + session + '&record_id=' + record_id) + res = TorrentTvApi._check(xmlresult) + stream_type = res.getElementsByTagName('type')[0].firstChild.data + source = res.getElementsByTagName('source')[0].firstChild.data + return stream_type.encode('utf-8'), source.encode('utf-8') + + @staticmethod + def _check(xmlresult): + """ + Validates received API answer + Raises an exception if error detected + + :param xmlresult: API answer to check + :return: minidom-parsed xmlresult + :raise: TorrentTvApiException + """ + res = dom.parseString(xmlresult).documentElement + success = res.getElementsByTagName('success')[0].firstChild.data + if success == '0' or not success: + error = res.getElementsByTagName('error')[0].firstChild.data + raise TorrentTvApiException('API returned error: ' + error) + return res + + @staticmethod + def _result(request): + """ + Sends request to API and returns the result in form of string + + :param request: API command string + :return: result of request to API + :raise: TorrentTvApiException + """ + try: + result = urllib2.urlopen('http://api.torrent-tv.ru/' + request + '&typeresult=xml', timeout=10).read() + return result + except (urllib2.URLError, socket.timeout) as e: + raise TorrentTvApiException('Error happened while trying to access API: ' + repr(e)) \ No newline at end of file diff --git a/vlcclient/vlcclient.py b/vlcclient/vlcclient.py index b222699..048fd2f 100644 --- a/vlcclient/vlcclient.py +++ b/vlcclient/vlcclient.py @@ -25,8 +25,8 @@ class VlcClient(object): ''' def __init__( - self, host='127.0.0.1', port=4212, password='admin', connect_timeout=5, - result_timeout=5, out_port=8081): + self, host='127.0.0.1', port=4212, password='admin', connect_timeout=10, + result_timeout=10, out_port=8081): # Receive buffer self._recvbuffer = None # Output port diff --git a/vlcclient/vlcmessages.py b/vlcclient/vlcmessages.py index da8606c..d2ebe45 100644 --- a/vlcclient/vlcmessages.py +++ b/vlcclient/vlcmessages.py @@ -13,6 +13,9 @@ class VlcMessage(object): command = 'new "' + stream_name + '" broadcast input "' + input + '" output ' + (pre_access + ':' if pre_access else '#') if qtype=='mp4': command = command + 'transcode{vcodec=mp4v,acodec=mpga,vb=800,ab=128}:' + elif qtype=='webm': + command = command + 'transcode{vcodec=VP80,acodec=vorbis,vb=512,ab=64}:' + muxer='ffmpeg{mux=webm}' command = command +'http{mux=' + muxer + ',dst=:' + \ str(out_port) + '/' + stream_name + '} option sout-keep option sout-all enabled' + \ "\r\n" + 'control "' + stream_name + '" play' diff --git a/vpconfig.py b/vpconfig.py index 05ecbbf..97d24fb 100644 --- a/vpconfig.py +++ b/vpconfig.py @@ -8,7 +8,7 @@ import logging class VPConfig(): # Message level (DEBUG, INFO, WARNING, ERROR, CRITICAL) - debug = logging.DEBUG + debug = logging.INFO # HTTP Server host httphost = '0.0.0.0' # HTTP Server port @@ -30,7 +30,7 @@ class VPConfig(): # Maximum concurrent connections (video clients) maxconns = 20 # Logging to a file - loggingtoafile = False + loggingtoafile = True # Path for logs, default is current directory. For example '/tmp/' logpath = '/var/log/vpproxy/' # @@ -38,9 +38,6 @@ class VPConfig(): # VLC configuration # ---------------------------------------------------- # - vlcuse = True - # Spawn VLC automaticaly - vlcspawn = True # VLC cmd line (use `--file-logging --logfile=filepath` to write log) vlccmd = "vlc -I telnet --clock-jitter 0 --network-caching 500 --sout-mux-caching 2000 --telnet-password admin --telnet-port 4212" # VLC spawn timeout @@ -67,21 +64,6 @@ class VPConfig(): vlcmux = 'ts' # Force ffmpeg INPUT demuxer in VLC. Sometimes can help. vlcforceffmpeg = False - # Stream start delay for dumb players (in seconds) - # !!! - # PLEASE set this to 0 if you use VLC - # !!! - videodelay = 0 - # Stream send delay after PAUSE/RESUME commands (works only if option - # above is enabled) - # !!! - # PLEASE set this to 0 if you use VLC - # !!! - videopausedelay = 0 - # Seek back feature. - # Seeks stream back for specified amount of seconds. - # Set it to 30 or so. - videoseekback = 5 # Delay before closing connection when client disconnects # In seconds. videodestroydelay = 5 diff --git a/vphttp.py b/vphttp.py index 5237f21..6108bd8 100644 --- a/vphttp.py +++ b/vphttp.py @@ -9,8 +9,9 @@ import traceback import gevent import gevent.monkey # Monkeypatching and all the stuff - gevent.monkey.patch_all() +# Startup delay for daemon restart +gevent.sleep(5) import glob import os import signal @@ -163,7 +164,7 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): self.reqtype = self.splittedpath[1].lower() # If first parameter is 'pid' or 'torrent' or it should be handled # by plugin - if not (self.reqtype in ('get','mp4') or self.reqtype in VPStuff.pluginshandlers): + if not (self.reqtype in ('get','mp4','webm') or self.reqtype in VPStuff.pluginshandlers): self.dieWithError(400) # 400 Bad Request return except IndexError: @@ -256,7 +257,7 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): else: self.vlcprefix = '' - logger.debug("Ready to start broadcast....") + logger.info("Starting broadcasting "+self.path) VPStuff.vlcclient.startBroadcast( self.vlcid, self.vlcprefix + self.path_unquoted, VPConfig.vlcmux, VPConfig.vlcpreaccess, self.reqtype) # Sleep a bit, because sometimes VLC doesn't open port in @@ -317,10 +318,12 @@ class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): self.dieWithError() finally: logger.debug("END REQUEST") + logger.info("Closed connection from " + self.clientip + " path " + self.path) VPStuff.clientcounter.delete(self.reqtype+'\\'+self.path_unquoted, self.clientip) if not VPStuff.clientcounter.get(self.reqtype+'\\'+self.path_unquoted): try: logger.debug("That was the last client, destroying VPClient") + logger.info("Stopping broadcasting " + self.path) VPStuff.vlcclient.stopBroadcast(self.vlcid) except: pass -- 2.34.1