def transcode(isQuery, inFile, outFile, tsn="", mime="", thead=""): vcodec = select_videocodec(inFile, tsn, mime) settings = select_buffsize(tsn) + vcodec if not vcodec[1] == "copy": settings += ( select_videobr(inFile, tsn) + select_maxvideobr(tsn) + select_videofps(inFile, tsn) + select_aspect(inFile, tsn) ) acodec = select_audiocodec(isQuery, inFile, tsn) settings += acodec if not acodec[1] == "copy": settings += select_audiobr(tsn) + select_audiofr(inFile, tsn) + select_audioch(inFile, tsn) settings += [select_audiolang(inFile, tsn), select_ffmpegprams(tsn)] settings += select_format(tsn, mime) settings = " ".join(settings).split() if isQuery: return settings ffmpeg_path = config.get_bin("ffmpeg") fname = unicode(inFile, "utf-8") if mswindows: fname = fname.encode("cp1252") if inFile[-5:].lower() == ".tivo": tivodecode_path = config.get_bin("tivodecode") tivo_mak = config.get_server("tivo_mak") tcmd = [tivodecode_path, "-m", tivo_mak, fname] tivodecode = subprocess.Popen(tcmd, stdout=subprocess.PIPE, bufsize=(512 * 1024)) if tivo_compatible(inFile, tsn)[0]: cmd = "" ffmpeg = tivodecode else: cmd = [ffmpeg_path, "-i", "-"] + settings ffmpeg = subprocess.Popen(cmd, stdin=tivodecode.stdout, stdout=subprocess.PIPE, bufsize=(512 * 1024)) else: cmd = [ffmpeg_path, "-i", fname] + settings ffmpeg = subprocess.Popen(cmd, bufsize=(512 * 1024), stdout=subprocess.PIPE) if cmd: debug("transcoding to tivo model " + tsn[:3] + " using ffmpeg command:") debug(" ".join(cmd)) ffmpeg_procs[inFile] = {"process": ffmpeg, "start": 0, "end": 0, "last_read": time.time(), "blocks": []} if thead: ffmpeg_procs[inFile]["blocks"].append(thead) reap_process(inFile) return resume_transfer(inFile, outFile, 0)
def transcode(isQuery, inFile, outFile, tsn='', mime='', thead=''): settings = {'video_codec': select_videocodec(inFile, tsn, mime), 'video_br': select_videobr(inFile, tsn), 'video_fps': select_videofps(inFile, tsn), 'max_video_br': select_maxvideobr(tsn), 'buff_size': select_buffsize(tsn), 'aspect_ratio': ' '.join(select_aspect(inFile, tsn)), 'audio_br': select_audiobr(tsn), 'audio_fr': select_audiofr(inFile, tsn), 'audio_ch': select_audioch(inFile, tsn), 'audio_codec': select_audiocodec(isQuery, inFile, tsn), 'audio_lang': select_audiolang(inFile, tsn), 'ffmpeg_pram': select_ffmpegprams(tsn), 'ffmpeg_threads': select_ffmpegthreads(), 'format': select_format(tsn, mime)} if isQuery: return settings ffmpeg_path = config.get_bin('ffmpeg') cmd_string = config.getFFmpegTemplate(tsn) % settings fname = unicode(inFile, 'utf-8') if mswindows: fname = fname.encode('iso8859-1') if inFile[-5:].lower() == '.tivo': tivodecode_path = config.get_bin('tivodecode') tivo_mak = config.get_server('tivo_mak') tcmd = [tivodecode_path, '-m', tivo_mak, fname] tivodecode = subprocess.Popen(tcmd, stdout=subprocess.PIPE, bufsize=(512 * 1024)) if tivo_compatible(inFile, tsn)[0]: cmd = '' ffmpeg = tivodecode else: cmd = [ffmpeg_path] + select_ffmpegthreads().split() + ['-i', '-'] + cmd_string.split() ffmpeg = subprocess.Popen(cmd, stdin=tivodecode.stdout, stdout=subprocess.PIPE, bufsize=(512 * 1024)) else: cmd = [ffmpeg_path] + select_ffmpegthreads().split() + ['-i', fname] + cmd_string.split() ffmpeg = subprocess.Popen(cmd, bufsize=(512 * 1024), stdout=subprocess.PIPE) if cmd: debug('transcoding to tivo model ' + tsn[:3] + ' using ffmpeg command:') debug(' '.join(cmd)) ffmpeg_procs[inFile] = {'process': ffmpeg, 'start': 0, 'end': 0, 'last_read': time.time(), 'blocks': []} if thead: ffmpeg_procs[inFile]['blocks'].append(thead) reap_process(inFile) return resume_transfer(inFile, outFile, 0)
def send_file(self, handler, path, query): seek = int(query.get('Seek', [0])[0]) duration = int(query.get('Duration', [0])[0]) always = (handler.container.getboolean('force_ffmpeg') and config.get_bin('ffmpeg')) fname = unicode(path, 'utf-8') ext = os.path.splitext(fname)[1].lower() needs_transcode = ext in TRANSCODE or seek or duration or always if not needs_transcode: fsize = os.path.getsize(fname) handler.send_response(200) handler.send_header('Content-Length', fsize) else: handler.send_response(206) handler.send_header('Transfer-Encoding', 'chunked') handler.send_header('Content-Type', 'audio/mpeg') handler.end_headers() if needs_transcode: if mswindows: fname = fname.encode('cp1252') cmd = [config.get_bin('ffmpeg'), '-i', fname, '-vn'] if ext in ['.mp3', '.mp2']: cmd += ['-acodec', 'copy'] else: cmd += ['-ab', '320k', '-ar', '44100'] cmd += ['-f', 'mp3', '-'] if seek: cmd[-1:] = ['-ss', '%.3f' % (seek / 1000.0), '-'] if duration: cmd[-1:] = ['-t', '%.3f' % (duration / 1000.0), '-'] ffmpeg = subprocess.Popen(cmd, bufsize=BLOCKSIZE, stdout=subprocess.PIPE) while True: try: block = ffmpeg.stdout.read(BLOCKSIZE) handler.wfile.write('%x\r\n' % len(block)) handler.wfile.write(block) handler.wfile.write('\r\n') except Exception, msg: handler.server.logger.info(msg) kill(ffmpeg) break if not block: break
def send_file(self, handler, path, query): seek = int(query.get('Seek', [0])[0]) duration = int(query.get('Duration', [0])[0]) always = (handler.container.getboolean('force_ffmpeg') and config.get_bin('ffmpeg')) fname = unicode(path, 'utf-8') ext = os.path.splitext(fname)[1].lower() needs_transcode = ext in TRANSCODE or seek or duration or always if not needs_transcode: fsize = os.path.getsize(fname) handler.send_response(200) handler.send_header('Content-Length', fsize) else: handler.send_response(206) handler.send_header('Transfer-Encoding', 'chunked') handler.send_header('Content-Type', 'audio/mpeg') handler.end_headers() if needs_transcode: if mswindows: fname = fname.encode('iso8859-1') cmd = [config.get_bin('ffmpeg'), '-i', fname, '-vn'] if ext in ['.mp3', '.mp2']: cmd += ['-acodec', 'copy'] else: cmd += ['-ab', '320k', '-ar', '44100'] cmd += ['-f', 'mp3', '-'] if seek: cmd[-1:] = ['-ss', '%.3f' % (seek / 1000.0), '-'] if duration: cmd[-1:] = ['-t', '%.3f' % (duration / 1000.0), '-'] ffmpeg = subprocess.Popen(cmd, bufsize=BLOCKSIZE, stdout=subprocess.PIPE) while True: try: block = ffmpeg.stdout.read(BLOCKSIZE) handler.wfile.write('%x\r\n' % len(block)) handler.wfile.write(block) handler.wfile.write('\r\n') except Exception, msg: handler.server.logger.info(msg) kill(ffmpeg) break if not block: break
def transcode(isQuery, inFile, outFile, tsn=""): settings = { "video_codec": select_videocodec(inFile, tsn), "video_br": select_videobr(inFile, tsn), "video_fps": select_videofps(inFile, tsn), "max_video_br": select_maxvideobr(tsn), "buff_size": select_buffsize(tsn), "aspect_ratio": " ".join(select_aspect(inFile, tsn)), "audio_br": select_audiobr(tsn), "audio_fr": select_audiofr(inFile, tsn), "audio_ch": select_audioch(tsn), "audio_codec": select_audiocodec(isQuery, inFile, tsn), "audio_lang": select_audiolang(inFile, tsn), "ffmpeg_pram": select_ffmpegprams(tsn), "format": select_format(tsn), } if isQuery: return settings ffmpeg_path = config.get_bin("ffmpeg") cmd_string = config.getFFmpegTemplate(tsn) % settings fname = unicode(inFile, "utf-8") if mswindows: fname = fname.encode("iso8859-1") if inFile[-5:].lower() == ".tivo": tivodecode_path = config.get_bin("tivodecode") tivo_mak = config.get_server("tivo_mak") tcmd = [tivodecode_path, "-m", tivo_mak, fname] tivodecode = subprocess.Popen(tcmd, stdout=subprocess.PIPE, bufsize=(512 * 1024)) if tivo_compatible(inFile, tsn)[0]: cmd = "" ffmpeg = tivodecode else: cmd = [ffmpeg_path, "-i", "-"] + cmd_string.split() ffmpeg = subprocess.Popen(cmd, stdin=tivodecode.stdout, stdout=subprocess.PIPE, bufsize=(512 * 1024)) else: cmd = [ffmpeg_path, "-i", fname] + cmd_string.split() ffmpeg = subprocess.Popen(cmd, bufsize=(512 * 1024), stdout=subprocess.PIPE) if cmd: debug("transcoding to tivo model " + tsn[:3] + " using ffmpeg command:") debug(" ".join(cmd)) ffmpeg_procs[inFile] = {"process": ffmpeg, "start": 0, "end": 0, "last_read": time.time(), "blocks": []} reap_process(inFile) return transfer_blocks(inFile, outFile)
def mp4_remux(inFile, basename, tsn=""): outFile = inFile + ".pyTivo-temp" newname = basename + ".pyTivo-temp" if os.path.exists(outFile): return None # ugh! ffmpeg_path = config.get_bin("ffmpeg") fname = unicode(inFile, "utf-8") oname = unicode(outFile, "utf-8") if mswindows: fname = fname.encode("cp1252") oname = oname.encode("cp1252") acodec = select_audiocodec(False, inFile, tsn, "video/mp4") settings = select_buffsize(tsn) + ["-c:v", "copy"] + acodec if not acodec[1] == "copy": settings += select_audiobr(tsn) + select_audiofr(inFile, tsn) + select_audioch(inFile, tsn) settings += [select_audiolang(inFile, tsn), select_ffmpegprams(tsn), "-f", "mp4"] cmd = [ffmpeg_path, "-i", fname] + " ".join(settings).split() + [oname] debug("transcoding to tivo model " + tsn[:3] + " using ffmpeg command:") debug(" ".join(cmd)) ffmpeg = subprocess.Popen(cmd) debug("remuxing " + inFile + " to " + outFile) if ffmpeg.wait(): debug("error during remuxing") os.remove(outFile) return None return newname
def tivo_compatible(inFile, tsn='', mime=''): vInfo = video_info(inFile) message = (True, 'all compatible') if not config.get_bin('ffmpeg'): if mime not in ['video/x-tivo-mpeg', 'video/mpeg', '']: message = (False, 'no ffmpeg') return message while True: vmessage = tivo_compatible_video(vInfo, tsn, mime) if not vmessage[0]: message = vmessage break amessage = tivo_compatible_audio(vInfo, inFile, tsn, mime) if not amessage[0]: message = amessage break cmessage = tivo_compatible_container(vInfo, inFile, mime) if not cmessage[0]: message = cmessage break debug('TRANSCODE=%s, %s, %s' % (['YES', 'NO'][message[0]], message[1], inFile)) return message
def detect_h264_level(fname, vstream=0): ffprobe_path = config.get_bin('ffprobe') if not ffprobe_path: return 0 cmd = [ffprobe_path, '-of', 'flat', '-show_streams', '-i', fname] # Windows and other OS buffer 4096 and ffprobe can output more than that. out_tmp = tempfile.TemporaryFile() ffprobe = subprocess.Popen(cmd, stdout=out_tmp, stderr=subprocess.PIPE, stdin=subprocess.PIPE) # wait configured # of seconds: if ffprobe is not back give up limit = config.getFFmpegWait() if limit: for i in xrange(limit * 20): time.sleep(.05) if not ffprobe.poll() == None: break if ffprobe.poll() == None: kill(ffprobe) return 0 else: ffprobe.wait() out_tmp.seek(0) output = out_tmp.read() out_tmp.close() debug('ffprobe output=%s' % repr(output)) match = re.search(r'streams\.stream\.\d+\.level=(\d+)', output) if match: return int(match.group(1)) return 0
def tivo_compatible(inFile, tsn="", mime=""): vInfo = video_info(inFile) message = (True, "all compatible") if not config.get_bin("ffmpeg"): if mime not in ["video/x-tivo-mpeg", "video/mpeg", ""]: message = (False, "no ffmpeg") return message while True: vmessage = tivo_compatible_video(vInfo, tsn, mime) if not vmessage[0]: message = vmessage break amessage = tivo_compatible_audio(vInfo, inFile, tsn, mime) if not amessage[0]: message = amessage break cmessage = tivo_compatible_container(vInfo, inFile, mime) if not cmessage[0]: message = cmessage break debug("TRANSCODE=%s, %s, %s" % (["YES", "NO"][message[0]], message[1], inFile)) return message
def send_file(self, handler, path, query): seek = int(query.get('Seek', [0])[0]) duration = int(query.get('Duration', [0])[0]) always = (handler.container.get('force_ffmpeg', 'False').lower() == 'true' and config.get_bin('ffmpeg')) fname = unicode(path, 'utf-8') ext = os.path.splitext(fname)[1].lower() needs_transcode = ext in TRANSCODE or seek or duration or always handler.send_response(200) handler.send_header('Content-Type', 'audio/mpeg') if not needs_transcode: fsize = os.path.getsize(fname) handler.send_header('Content-Length', fsize) handler.send_header('Connection', 'close') handler.end_headers() if needs_transcode: if mswindows: fname = fname.encode('iso8859-1') cmd = [config.get_bin('ffmpeg'), '-i', fname] if ext in ['.mp3', '.mp2']: cmd += ['-acodec', 'copy'] else: cmd += ['-ab', '320k', '-ar', '44100'] cmd += ['-f', 'mp3', '-'] if seek: cmd[-1:] = ['-ss', '%.3f' % (seek / 1000.0), '-'] if duration: cmd[-1:] = ['-t', '%.3f' % (duration / 1000.0), '-'] ffmpeg = subprocess.Popen(cmd, bufsize=(64 * 1024), stdout=subprocess.PIPE) try: shutil.copyfileobj(ffmpeg.stdout, handler.wfile) except: kill(ffmpeg) else: f = open(fname, 'rb') try: shutil.copyfileobj(f, handler.wfile) except: pass f.close()
def do_update_git(pyTivo_dir, latest): git_path = config.get_bin('git') output, err = None, None logger.debug('Git executable path is: %s' % git_path) if git_path: try: # this may not be syncing with GitHub but another remote # it depends on the local git repository configuration exec_git = subprocess.Popen([git_path, 'pull origin', GIT_BRANCH], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=pyTivo_dir) output, err = exec_git.communicate() if 'not a git command' in output: # try alternate method if pull origin fails exec_git = subprocess.Popen([git_path, 'pull'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=pyTivo_dir) output, err = exec_git.communicate() logger.debug('Git output:\n%s' % output) if err: logger.debug('Git error output: %s' % err) except OSError, e: logger.error('There was an error during Git execution: %s' % e) return False if not output or err: logger.error('Git produced no output') return False # catch fatal error from Git # reason is usually cwd is not a proper git install or is corrupt if 'fatal:' in output or err: logger.error('Install corrupt or not a git repository:\n%s' % pyTivo_dir) return False if 'error:' in output or err: logger.error('Unable to update existing files') return 'no_permission' # catch already up-to-date git status # if user sees this there may be a bug in the current version detection # or are using an out of date remote repository that is out of sync with GitHub. if 'Already up-to-date' in output: logger.error('Version mismatch. Local git may be fetching ' + 'from an outdated remote repository.') logger.info('Recommended to use GitHub repository instead from:\n%s' % PACKAGE_URL.split('tarball')[0]) return False # check for successful output at end of git pull. # typically will be indication of files changed, insertions, or deletions if ('changed' and 'insertions' and 'deletions') in output: return True else: logger.error('Can not determine if Git pull was successful; assuming failure') return False
def __init__(self, tivoIP, active_tivos, active_tivos_lock, tivo_open): """ Initialize the TivoDownload with the IP address of the tivo in the active tivo list whose queue is to be processed and a function to open a tivo download url. """ super().__init__() self.tivoIP = tivoIP self.active_tivos = active_tivos self.active_tivos_lock = active_tivos_lock self.tivo_open = tivo_open with self.active_tivos_lock: self.tivo_tasks = self.active_tivos[tivoIP] # prefer tivolibre to tivodecode self.decoder_path = config.get_bin('tivolibre') self.decoder_is_tivolibre = True if not self.decoder_path: self.decoder_path = config.get_bin('tivodecode') self.decoder_is_tivolibre = False self.has_decoder = bool(self.decoder_path)
def pad_check(): global pad_style if pad_style == UNSET: pad_style = OLD_PAD filters = tempfile.TemporaryFile() cmd = [config.get_bin('ffmpeg'), '-filters'] ffmpeg = subprocess.Popen(cmd, stdout=filters, stderr=subprocess.PIPE) ffmpeg.wait() filters.seek(0) for line in filters: if line.startswith('pad'): pad_style = NEW_PAD break filters.close() return pad_style == NEW_PAD
def AudioFileFilter(f, filter_type=None): ext = os.path.splitext(f)[1].lower() if ext in ('.mp3', '.mp2') or (ext in TRANSCODE and config.get_bin('ffmpeg')): return self.AUDIO else: file_type = False if not filter_type or filter_type.split('/')[0] != self.AUDIO: if ext in PLAYLISTS: file_type = self.PLAYLIST elif os.path.isdir(f): file_type = self.DIRECTORY return file_type
def mp4_remux(inFile, basename, tsn='', temp_share_path=''): outFile = inFile + '.pyTivo-temp' newname = basename + '.pyTivo-temp' if temp_share_path: newname = os.path.splitext(os.path.split(basename)[1])[0] + '.mp4.pyTivo-temp' outFile = os.path.join(temp_share_path, newname) if os.path.exists(outFile): return None # ugh! ffmpeg_path = config.get_bin('ffmpeg') fname = unicode(inFile, 'utf-8') oname = unicode(outFile, 'utf-8') if mswindows: fname = fname.encode('iso8859-1') oname = oname.encode('iso8859-1') settings = {'video_codec': '-vcodec copy', 'video_br': select_videobr(inFile, tsn), 'video_fps': select_videofps(inFile, tsn), 'max_video_br': select_maxvideobr(tsn), 'buff_size': select_buffsize(tsn), 'aspect_ratio': ' '.join(select_aspect(inFile, tsn)), 'audio_br': select_audiobr(tsn), 'audio_fr': select_audiofr(inFile, tsn), 'audio_ch': select_audioch(inFile, tsn), 'audio_codec': select_audiocodec(False, inFile, tsn, 'video/mp4'), 'audio_lang': select_audiolang(inFile, tsn), 'ffmpeg_pram': select_ffmpegprams(tsn), 'format': '-f mp4'} cmd_string = config.getFFmpegTemplate(tsn) % settings cmd = [ffmpeg_path, '-i', fname] + cmd_string.split() + [oname] debug('transcoding to tivo model ' + tsn[:3] + ' using ffmpeg command:') debug(' '.join(cmd)) ffmpeg = subprocess.Popen(cmd) debug('remuxing ' + inFile + ' to ' + outFile) if ffmpeg.wait(): debug('error during remuxing') os.remove(outFile) return None return newname
def from_tivo(full_path): if full_path in tivo_cache: return tivo_cache[full_path] tdcat_path = config.get_bin('tdcat') tivo_mak = config.get_server('tivo_mak') try: assert (tivo_mak) if tdcat_path: details = _tdcat_bin(tdcat_path, full_path, tivo_mak) else: details = _tdcat_py(full_path, tivo_mak) metadata = from_details(details) tivo_cache[full_path] = metadata except: metadata = {} return metadata
def from_tivo(full_path): if full_path in tivo_cache: return tivo_cache[full_path] tdcat_path = config.get_bin("tdcat") tivo_mak = config.get_server("tivo_mak") try: assert tivo_mak if tdcat_path: details = _tdcat_bin(tdcat_path, full_path, tivo_mak) else: details = _tdcat_py(full_path, tivo_mak) metadata = from_details(details) tivo_cache[full_path] = metadata except: metadata = {} return metadata
def from_tivo(full_path): if full_path in tivo_cache: return tivo_cache[full_path] tdcat_path = config.get_bin('tdcat') tivo_mak = config.get_server('tivo_mak') if tdcat_path and tivo_mak: fname = unicode(full_path, 'utf-8') if mswindows: fname = fname.encode('iso8859-1') tcmd = [tdcat_path, '-m', tivo_mak, '-2', fname] tdcat = subprocess.Popen(tcmd, stdout=subprocess.PIPE) metadata = from_details(tdcat.stdout) tivo_cache[full_path] = metadata else: metadata = {} return metadata
def mp4_remux(inFile, basename, tsn=''): outFile = inFile + '.pyTivo-temp' newname = basename + '.pyTivo-temp' if os.path.exists(outFile): return None # ugh! ffmpeg_path = config.get_bin('ffmpeg') fname = unicode(inFile, 'utf-8') oname = unicode(outFile, 'utf-8') if mswindows: fname = fname.encode('iso8859-1') oname = oname.encode('iso8859-1') settings = { 'video_codec': '-vcodec copy', 'video_br': select_videobr(inFile, tsn), 'video_fps': select_videofps(inFile, tsn), 'max_video_br': select_maxvideobr(tsn), 'buff_size': select_buffsize(tsn), 'aspect_ratio': ' '.join(select_aspect(inFile, tsn)), 'audio_br': select_audiobr(tsn), 'audio_fr': select_audiofr(inFile, tsn), 'audio_ch': select_audioch(inFile, tsn), 'audio_codec': select_audiocodec(False, inFile, tsn, 'video/mp4'), 'audio_lang': select_audiolang(inFile, tsn), 'ffmpeg_pram': select_ffmpegprams(tsn), 'format': '-f mp4' } cmd_string = config.getFFmpegTemplate(tsn) % settings cmd = [ffmpeg_path, '-i', fname] + cmd_string.split() + [oname] debug('transcoding to tivo model ' + tsn[:3] + ' using ffmpeg command:') debug(' '.join(cmd)) ffmpeg = subprocess.Popen(cmd) debug('remuxing ' + inFile + ' to ' + outFile) if ffmpeg.wait(): debug('error during remuxing') os.remove(outFile) return None return newname
def from_tivo(full_path): if full_path in tivo_cache: return tivo_cache[full_path] tdcat_path = config.get_bin('tdcat') tivo_mak = config.get_server('tivo_mak') try: assert (tdcat_path and tivo_mak) fname = unicode(full_path, 'utf-8') if mswindows: fname = fname.encode('iso8859-1') tcmd = [tdcat_path, '-m', tivo_mak, '-2', fname] tdcat = subprocess.Popen(tcmd, stdout=subprocess.PIPE) metadata = from_details(tdcat.stdout) tivo_cache[full_path] = metadata except: metadata = {} return metadata
def mp4_remux(inFile, basename, tsn='', temp_share_path=''): outFile = inFile + '.pyTivo-temp' newname = basename + '.pyTivo-temp' if temp_share_path: newname = os.path.splitext(os.path.split(basename)[1])[0] + '.mp4.pyTivo-temp' outFile = os.path.join(temp_share_path, newname) if os.path.exists(outFile): return None # ugh! ffmpeg_path = config.get_bin('ffmpeg') fname = unicode(inFile, 'utf-8') oname = unicode(outFile, 'utf-8') if mswindows: fname = fname.encode('cp1252') oname = oname.encode('cp1252') acodec = select_audiocodec(False, inFile, tsn, 'video/mp4') settings = select_buffsize(tsn) + ['-c:v', 'copy'] + acodec if not acodec[1] == 'copy': settings += (select_audiobr(tsn) + select_audiofr(inFile, tsn) + select_audioch(inFile, tsn)) settings += [select_audiolang(inFile, tsn), select_ffmpegprams(tsn), '-f', 'mp4'] cmd = [ffmpeg_path, '-i', fname] + ' '.join(settings).split() + [oname] debug('transcoding to tivo model ' + tsn[:3] + ' using ffmpeg command:') debug(' '.join(cmd)) ffmpeg = subprocess.Popen(cmd) debug('remuxing ' + inFile + ' to ' + outFile) if ffmpeg.wait(): debug('error during remuxing') os.remove(outFile) return None return newname
def audio_check(inFile, tsn): cmd_string = ('-y -vcodec mpeg2video -r 29.97 -b 1000k -acodec copy ' + select_audiolang(inFile, tsn) + ' -t 00:00:01 -f vob -') fname = unicode(inFile, 'utf-8') if mswindows: fname = fname.encode('iso8859-1') cmd = [config.get_bin('ffmpeg'), '-i', fname] + cmd_string.split() ffmpeg = subprocess.Popen(cmd, stdout=subprocess.PIPE) fd, testname = tempfile.mkstemp() testfile = os.fdopen(fd, 'wb') try: shutil.copyfileobj(ffmpeg.stdout, testfile) except: kill(ffmpeg) testfile.close() vInfo = None else: testfile.close() vInfo = video_info(testname, False) os.remove(testname) return vInfo
def audio_check(inFile, tsn): cmd_string = ('-y -c:v mpeg2video -r 29.97 -b:v 1000k -c:a copy ' + select_audiolang(inFile, tsn) + ' -t 00:00:01 -f vob -') fname = unicode(inFile, 'utf-8') if mswindows: fname = fname.encode('cp1252') cmd = [config.get_bin('ffmpeg'), '-i', fname] + cmd_string.split() ffmpeg = subprocess.Popen(cmd, stdout=subprocess.PIPE) fd, testname = tempfile.mkstemp() testfile = os.fdopen(fd, 'wb') try: shutil.copyfileobj(ffmpeg.stdout, testfile) except: kill(ffmpeg) testfile.close() vInfo = None else: testfile.close() vInfo = video_info(testname, False) os.remove(testname) return vInfo
def audio_check(inFile, tsn): cmd_string = ( "-y -c:v mpeg2video -r 29.97 -b:v 1000k -c:a copy " + select_audiolang(inFile, tsn) + " -t 00:00:01 -f vob -" ) fname = unicode(inFile, "utf-8") if mswindows: fname = fname.encode("cp1252") cmd = [config.get_bin("ffmpeg"), "-i", fname] + cmd_string.split() ffmpeg = subprocess.Popen(cmd, stdout=subprocess.PIPE) fd, testname = tempfile.mkstemp() testfile = os.fdopen(fd, "wb") try: shutil.copyfileobj(ffmpeg.stdout, testfile) except: kill(ffmpeg) testfile.close() vInfo = None else: testfile.close() vInfo = video_info(testname, False) os.remove(testname) return vInfo
def mp4_remux(inFile, basename): outFile = inFile + '.pyTivo-temp' newname = basename + '.pyTivo-temp' if os.path.exists(outFile): return None # ugh! ffmpeg_path = config.get_bin('ffmpeg') fname = unicode(inFile, 'utf-8') oname = unicode(outFile, 'utf-8') if mswindows: fname = fname.encode('iso8859-1') oname = oname.encode('iso8859-1') cmd = [ffmpeg_path, '-i', fname, '-vcodec', 'copy', '-acodec', 'copy', '-f', 'mp4', oname] ffmpeg = subprocess.Popen(cmd) debug('remuxing ' + inFile + ' to ' + outFile) if ffmpeg.wait(): debug('error during remuxing') os.remove(outFile) return None return newname
def audio_check(inFile, tsn): """ Get the video_info for a test transcode of the desired audio stream by transcoding one second of the source file and extracting its info. """ # create an empty temporary file to be written to by ffmpeg fd, testname = tempfile.mkstemp() os.close(fd) cmd = [config.get_bin('ffmpeg'), '-hide_banner', '-nostdin', '-i', inFile, '-y', '-c:v', 'mpeg2video', '-r', '29.97', '-b:v', '1000k', '-c:a', 'copy' ] + \ select_audiolang(inFile, tsn).split() + \ ['-t', '00:00:01', '-f', 'vob', testname ] try: ffmpeg = subprocess.run(cmd, timeout=10, stderr=subprocess.PIPE, encoding=locale.getpreferredencoding(), errors='replace') except subprocess.TimeoutExpired: debug('audio_check: ffmpeg timed out.\nffmpeg reported: {}'.format(ffmpeg.stderr)) vInfo = None else: vInfo = video_info(testname, False) os.remove(testname) return vInfo
def mp4_remux(inFile, basename, tsn=''): outFile = inFile + '.pyTivo-temp' newname = basename + '.pyTivo-temp' if os.path.exists(outFile): return None # ugh! ffmpeg_path = config.get_bin('ffmpeg') fname = unicode(inFile, 'utf-8') oname = unicode(outFile, 'utf-8') if mswindows: fname = fname.encode('cp1252') oname = oname.encode('cp1252') acodec = select_audiocodec(False, inFile, tsn, 'video/mp4') settings = select_buffsize(tsn) + ['-c:v', 'copy'] + acodec if not acodec[1] == 'copy': settings += (select_audiobr(tsn) + select_audiofr(inFile, tsn) + select_audioch(inFile, tsn)) settings += [ select_audiolang(inFile, tsn), select_ffmpegprams(tsn), '-f', 'mp4' ] cmd = [ffmpeg_path, '-i', fname] + ' '.join(settings).split() + [oname] debug('transcoding to tivo model ' + tsn[:3] + ' using ffmpeg command:') debug(' '.join(cmd)) ffmpeg = subprocess.Popen(cmd) debug('remuxing ' + inFile + ' to ' + outFile) if ffmpeg.wait(): debug('error during remuxing') os.remove(outFile) return None return newname
def transcode(isQuery, inFile, outFile, tsn='', mime='', thead=''): settings = { 'video_codec': select_videocodec(inFile, tsn), 'video_br': select_videobr(inFile, tsn), 'video_fps': select_videofps(inFile, tsn), 'max_video_br': select_maxvideobr(tsn), 'buff_size': select_buffsize(tsn), 'aspect_ratio': ' '.join(select_aspect(inFile, tsn)), 'audio_br': select_audiobr(tsn), 'audio_fr': select_audiofr(inFile, tsn), 'audio_ch': select_audioch(inFile, tsn), 'audio_codec': select_audiocodec(isQuery, inFile, tsn), 'audio_lang': select_audiolang(inFile, tsn), 'ffmpeg_pram': select_ffmpegprams(tsn), 'format': select_format(tsn, mime) } if isQuery: return settings ffmpeg_path = config.get_bin('ffmpeg') cmd_string = config.getFFmpegTemplate(tsn) % settings fname = unicode(inFile, 'utf-8') if mswindows: fname = fname.encode('iso8859-1') if inFile[-5:].lower() == '.tivo': tivodecode_path = config.get_bin('tivodecode') tivo_mak = config.get_server('tivo_mak') tcmd = [tivodecode_path, '-m', tivo_mak, fname] tivodecode = subprocess.Popen(tcmd, stdout=subprocess.PIPE, bufsize=(512 * 1024)) if tivo_compatible(inFile, tsn)[0]: cmd = '' ffmpeg = tivodecode else: cmd = [ffmpeg_path, '-i', '-'] + cmd_string.split() ffmpeg = subprocess.Popen(cmd, stdin=tivodecode.stdout, stdout=subprocess.PIPE, bufsize=(512 * 1024)) else: cmd = [ffmpeg_path, '-i', fname] + cmd_string.split() ffmpeg = subprocess.Popen(cmd, bufsize=(512 * 1024), stdout=subprocess.PIPE) if cmd: debug('transcoding to tivo model ' + tsn[:3] + ' using ffmpeg command:') debug(' '.join(cmd)) ffmpeg_procs[inFile] = { 'process': ffmpeg, 'start': 0, 'end': 0, 'last_read': time.time(), 'blocks': [] } if thead: ffmpeg_procs[inFile]['blocks'].append(thead) reap_process(inFile) return resume_transfer(inFile, outFile, 0)
def get_image_ffmpeg(self, path, width, height, pshape, rot, attrs): ffmpeg_path = config.get_bin('ffmpeg') if not ffmpeg_path: return False, 'FFmpeg not found' fname = unicode(path, 'utf-8') if sys.platform == 'win32': fname = fname.encode('cp1252') if attrs and 'size' in attrs: result = attrs['size'] else: status, result = self.get_size_ffmpeg(ffmpeg_path, fname) if not status: return False, result if attrs: attrs['size'] = result if rot in (90, 270): oldh, oldw = result else: oldw, oldh = result width, height = self.new_size(oldw, oldh, width, height, pshape) if rot == 270: filters = 'transpose=1,' elif rot == 180: filters = 'hflip,vflip,' elif rot == 90: filters = 'transpose=2,' else: filters = '' filters += 'format=yuvj420p,' neww, newh = oldw, oldh while (neww / width >= 50) or (newh / height >= 50): neww /= 2 newh /= 2 filters += 'scale=%d:%d,' % (neww, newh) filters += 'scale=%d:%d' % (width, height) cmd = [ffmpeg_path, '-i', fname, '-vf', filters, '-f', 'mjpeg', '-'] jpeg_tmp = tempfile.TemporaryFile() ffmpeg = subprocess.Popen(cmd, stdout=jpeg_tmp, stdin=subprocess.PIPE) # wait configured # of seconds: if ffmpeg is not back give up limit = config.getFFmpegWait() if limit: for i in xrange(limit * 20): time.sleep(.05) if not ffmpeg.poll() == None: break if ffmpeg.poll() == None: kill(ffmpeg) return False, 'FFmpeg timed out' else: ffmpeg.wait() jpeg_tmp.seek(0) output = jpeg_tmp.read() jpeg_tmp.close() if 'JFIF' not in output[:10]: output = output[:2] + JFIF_TAG + output[2:] return True, output
def send_file(self, handler, path, query): logger.debug('send_file entered...') seek = int(query.get('Seek', [0])[0]) duration = int(query.get('Duration', [0])[0]) always = (handler.container.getboolean('force_ffmpeg') and config.get_bin('ffmpeg')) fname = path ext = os.path.splitext(fname)[1].lower() needs_transcode = ext in TRANSCODE or seek or duration or always if not needs_transcode: fsize = os.path.getsize(fname) handler.send_response(200) handler.send_header('Content-Length', fsize) else: # use chunked transfer-encoding to send transcoded data as we get it # see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding handler.send_response(206) handler.send_header('Transfer-Encoding', 'chunked') handler.send_header('Content-Type', 'audio/mpeg') handler.end_headers() if needs_transcode: cmd = [config.get_bin('ffmpeg'), '-hide_banner', '-nostdin', '-i', fname, '-vn'] if ext in ['.mp3', '.mp2']: cmd += ['-acodec', 'copy'] else: cmd += ['-ab', '320k', '-ar', '44100'] cmd += ['-f', 'mp3', '-'] if seek: cmd[-1:] = ['-ss', '%.3f' % (seek / 1000.0), '-'] if duration: cmd[-1:] = ['-t', '%.3f' % (duration / 1000.0), '-'] logger.debug('start process: {}'.format(cmd)) ffmpeg = subprocess.Popen(cmd, bufsize=BLOCKSIZE, stdout=subprocess.PIPE) while True: try: block = ffmpeg.stdout.read(BLOCKSIZE) handler.wfile.write(b'%x\r\n' % len(block)) handler.wfile.write(block) handler.wfile.write(b'\r\n') except Exception as msg: handler.server.logger.info(msg) kill(ffmpeg) break if not block: break else: f = open(fname, 'rb') try: shutil.copyfileobj(f, handler.wfile) except: pass f.close() try: handler.wfile.flush() except Exception as msg: handler.server.logger.info(msg)
ts_format = True handle = self.tivo_open(url + '&Format=video/x-tivo-mpeg-ts') else: handle = self.tivo_open(url) except Exception, msg: status[url]['running'] = False status[url]['error'] = str(msg) return tivo_name = config.tivos[config.tivos_by_ip(tivoIP)].get( 'name', tivoIP) logger.info('[%s] Start getting "%s" from %s' % (time.strftime('%d/%b/%Y %H:%M:%S'), outfile, tivo_name)) has_tivodecode = bool(config.get_bin('tivodecode')) has_tivolibre = bool(config.get_bin('tivolibre')) decode = status[url]['decode'] and (has_tivodecode or has_tivolibre) if decode: fname = outfile if mswindows: fname = fname.encode('cp1252') decoder_path = config.get_bin('tivodecode') tcmd = [decoder_path, '-m', mak, '-o', fname, '-'] if has_tivolibre: decoder_path = config.get_bin('tivolibre') tcmd = [decoder_path, '-m', mak, '-o', fname] tivodecode = subprocess.Popen(tcmd, stdin=subprocess.PIPE,
def send_file(self, handler, path, query): mime = 'video/x-tivo-mpeg' tsn = handler.headers.getheader('tsn', '') tivo_name = config.tivo_names.get(tsn, tsn) is_tivo_file = (path[-5:].lower() == '.tivo') if 'Format' in query: mime = query['Format'][0] needs_tivodecode = (is_tivo_file and mime == 'video/mpeg') compatible = (not needs_tivodecode and transcode.tivo_compatible(path, tsn, mime)[0]) try: # "bytes=XXX-" offset = int(handler.headers.getheader('Range')[6:-1]) except: offset = 0 if needs_tivodecode: valid = bool(config.get_bin('tivodecode') and config.get_server('tivo_mak')) else: valid = True if valid and offset: valid = ((compatible and offset < os.stat(path).st_size) or (not compatible and transcode.is_resumable(path, offset))) #faking = (mime in ['video/x-tivo-mpeg-ts', 'video/x-tivo-mpeg'] and faking = (mime == 'video/x-tivo-mpeg' and not (is_tivo_file and compatible)) fname = unicode(path, 'utf-8') thead = '' if faking: thead = self.tivo_header(tsn, path, mime) if compatible: size = os.stat(fname).st_size + len(thead) handler.send_response(200) handler.send_header('Content-Length', size - offset) handler.send_header('Content-Range', 'bytes %d-%d/%d' % (offset, size - offset - 1, size)) else: handler.send_response(206) handler.send_header('Transfer-Encoding', 'chunked') handler.send_header('Content-Type', mime) handler.end_headers() logger.info('[%s] Start sending "%s" to %s' % (time.strftime('%d/%b/%Y %H:%M:%S'), fname, tivo_name)) start = time.time() count = 0 if valid: if compatible: if faking and not offset: handler.wfile.write(thead) logger.debug('"%s" is tivo compatible' % fname) f = open(fname, 'rb') try: if mime == 'video/mp4': count = qtfaststart.process(f, handler.wfile, offset) else: if offset: offset -= len(thead) f.seek(offset) while True: block = f.read(512 * 1024) if not block: break handler.wfile.write(block) count += len(block) except Exception, msg: logger.info(msg) f.close() else: logger.debug('"%s" is not tivo compatible' % fname) if offset: count = transcode.resume_transfer(path, handler.wfile, offset) else: count = transcode.transcode(False, path, handler.wfile, tsn, mime, thead)
def get_image_ffmpeg(self, path, width, height, pshape, rot, attrs): ffmpeg_path = config.get_bin('ffmpeg') if not ffmpeg_path: return False, 'FFmpeg not found' fname = unicode(path, 'utf-8') if sys.platform == 'win32': fname = fname.encode('iso8859-1') if attrs and 'size' in attrs: result = attrs['size'] else: status, result = self.get_size_ffmpeg(ffmpeg_path, fname) if not status: return False, result if attrs: attrs['size'] = result if rot in (90, 270): oldh, oldw = result else: oldw, oldh = result width, height = self.new_size(oldw, oldh, width, height, pshape) if rot == 270: filters = 'transpose=1,' elif rot == 180: filters = 'hflip,vflip,' elif rot == 90: filters = 'transpose=2,' else: filters = '' filters += 'format=yuvj420p,' neww, newh = oldw, oldh while (neww / width >= 50) or (newh / height >= 50): neww /= 2 newh /= 2 filters += 'scale=%d:%d,' % (neww, newh) filters += 'scale=%d:%d' % (width, height) cmd = [ffmpeg_path, '-i', fname, '-vf', filters, '-f', 'mjpeg', '-'] jpeg_tmp = tempfile.TemporaryFile() ffmpeg = subprocess.Popen(cmd, stdout=jpeg_tmp, stdin=subprocess.PIPE) # wait configured # of seconds: if ffmpeg is not back give up limit = config.getFFmpegWait() if limit: for i in xrange(limit * 20): time.sleep(.05) if not ffmpeg.poll() == None: break if ffmpeg.poll() == None: kill(ffmpeg) return False, 'FFmpeg timed out' else: ffmpeg.wait() jpeg_tmp.seek(0) output = jpeg_tmp.read() jpeg_tmp.close() if 'JFIF' not in output[:10]: output = output[:2] + JFIF_TAG + output[2:] return True, output
def NPL(self, handler, query): def getint(thing): try: result = int(thing) except: result = 0 return result global basic_meta shows_per_page = 50 # Change this to alter the number of shows returned folder = '' FirstAnchor = '' has_tivodecode = bool(config.get_bin('tivodecode')) useragent = handler.headers.getheader('User-Agent', '') if 'TiVo' in query: tivoIP = query['TiVo'][0] tsn = config.tivos_by_ip(tivoIP) tivo_name = config.tivo_names[tsn] tivo_mak = config.get_tsn('tivo_mak', tsn) theurl = ('https://' + tivoIP + '/TiVoConnect?Command=QueryContainer&ItemCount=' + str(shows_per_page) + '&Container=/NowPlaying') if 'Folder' in query: folder += query['Folder'][0] theurl += '/' + folder if 'AnchorItem' in query: theurl += '&AnchorItem=' + quote(query['AnchorItem'][0]) if 'AnchorOffset' in query: theurl += '&AnchorOffset=' + query['AnchorOffset'][0] if (theurl not in tivo_cache or (time.time() - tivo_cache[theurl]['thepage_time']) >= 60): # if page is not cached or old then retreive it auth_handler.add_password('TiVo DVR', tivoIP, 'tivo', tivo_mak) try: page = self.tivo_open(theurl) except IOError, e: handler.redir(UNABLE % tivoIP, 10) return tivo_cache[theurl] = {'thepage': minidom.parse(page), 'thepage_time': time.time()} page.close() xmldoc = tivo_cache[theurl]['thepage'] items = xmldoc.getElementsByTagName('Item') TotalItems = tag_data(xmldoc, 'TiVoContainer/Details/TotalItems') ItemStart = tag_data(xmldoc, 'TiVoContainer/ItemStart') ItemCount = tag_data(xmldoc, 'TiVoContainer/ItemCount') title = tag_data(xmldoc, 'TiVoContainer/Details/Title') if items: FirstAnchor = tag_data(items[0], 'Links/Content/Url') data = [] for item in items: entry = {} entry['ContentType'] = tag_data(item, 'Details/ContentType') for tag in ('CopyProtected', 'UniqueId'): value = tag_data(item, 'Details/' + tag) if value: entry[tag] = value if entry['ContentType'] == 'x-tivo-container/folder': entry['Title'] = tag_data(item, 'Details/Title') entry['TotalItems'] = tag_data(item, 'Details/TotalItems') lc = tag_data(item, 'Details/LastCaptureDate') if not lc: lc = tag_data(item, 'Details/LastChangeDate') entry['LastChangeDate'] = time.strftime('%b %d, %Y', time.localtime(int(lc, 16))) else: keys = {'Icon': 'Links/CustomIcon/Url', 'Url': 'Links/Content/Url', 'SourceSize': 'Details/SourceSize', 'Duration': 'Details/Duration', 'CaptureDate': 'Details/CaptureDate'} for key in keys: value = tag_data(item, keys[key]) if value: entry[key] = value rawsize = entry['SourceSize'] entry['SourceSize'] = metadata.human_size(rawsize) dur = getint(entry['Duration']) / 1000 entry['Duration'] = ( '%d:%02d:%02d' % (dur / 3600, (dur % 3600) / 60, dur % 60) ) entry['CaptureDate'] = time.strftime('%b %d, %Y', time.localtime(int(entry['CaptureDate'], 16))) url = entry['Url'] if url in basic_meta: entry.update(basic_meta[url]) else: basic_data = metadata.from_container(item) entry.update(basic_data) basic_meta[url] = basic_data data.append(entry)
status[url]['error'] = e.code logger.error(e.code) return except urllib2.URLError, e: status[url]['running'] = False status[url]['error'] = e.reason logger.error(e.reason) return tivo_name = config.tivo_names[config.tivos_by_ip(tivoIP)] logger.info('[%s] Start getting "%s" from %s' % (time.strftime('%d/%b/%Y %H:%M:%S'), outfile, tivo_name)) if status[url]['decode']: tivodecode_path = config.get_bin('tivodecode') tcmd = [tivodecode_path, '-m', mak, '-o', outfile, '-'] tivodecode = subprocess.Popen(tcmd, stdin=subprocess.PIPE, bufsize=(512 * 1024)) f = tivodecode.stdin else: f = open(outfile, 'wb') length = 0 start_time = time.time() last_interval = start_time now = start_time try: while status[url]['running']: output = handle.read(1024000) if not output: break
EXTENSIONS = """.tivo .mpg .avi .wmv .mov .flv .f4v .vob .mp4 .m4v .mkv .ts .tp .trp .3g2 .3gp .3gp2 .3gpp .amv .asf .avs .bik .bix .box .bsf .dat .dif .divx .dmb .dpg .dv .dvr-ms .evo .eye .flc .fli .flx .gvi .ivf .m1v .m21 .m2t .m2ts .m2v .m2p .m4e .mjp .mjpeg .mod .moov .movie .mp21 .mpe .mpeg .mpv .mpv2 .mqv .mts .mvb .nsv .nuv .nut .ogm .qt .rm .rmvb .rts .scm .smv .ssm .svi .vdo .vfw .vid .viv .vivo .vp6 .vp7 .vro .webm .wm .wmd .wtv .yuv""".split() LIKELYTS = """.ts .tp .trp .3g2 .3gp .3gp2 .3gpp .m2t .m2ts .mts .mp4 .m4v .flv .mkv .mov .wtv .dvr-ms .webm""".split() status = {} # Global variable to track uploads use_extensions = True try: assert (config.get_bin('ffmpeg')) except: use_extensions = False def uniso(iso): return time.strptime(iso[:19], '%Y-%m-%dT%H:%M:%S') def isodt(iso): return datetime(*uniso(iso)[:6]) def isogm(iso): return int(calendar.timegm(uniso(iso)))
def transcode(isQuery, inFile, outFile, tsn='', mime='', thead=''): vcodec = select_videocodec(inFile, tsn, mime) settings = select_buffsize(tsn) + vcodec if not vcodec[1] == 'copy': settings += (select_videobr(inFile, tsn) + select_maxvideobr(tsn) + select_videofps(inFile, tsn) + select_aspect(inFile, tsn)) acodec = select_audiocodec(isQuery, inFile, tsn) settings += acodec if not acodec[1] == 'copy': settings += (select_audiobr(tsn) + select_audiofr(inFile, tsn) + select_audioch(inFile, tsn)) settings += [select_audiolang(inFile, tsn), select_ffmpegprams(tsn)] settings += select_format(tsn, mime) settings = ' '.join(settings).split() if isQuery: return settings ffmpeg_path = config.get_bin('ffmpeg') fname = unicode(inFile, 'utf-8') if mswindows: fname = fname.encode('cp1252') if inFile[-5:].lower() == '.tivo': tivodecode_path = config.get_bin('tivodecode') tivo_mak = config.get_server('tivo_mak') tcmd = [tivodecode_path, '-m', tivo_mak, fname] tivodecode = subprocess.Popen(tcmd, stdout=subprocess.PIPE, bufsize=(512 * 1024)) if tivo_compatible(inFile, tsn)[0]: cmd = '' ffmpeg = tivodecode else: cmd = [ffmpeg_path, '-i', '-'] + settings ffmpeg = subprocess.Popen(cmd, stdin=tivodecode.stdout, stdout=subprocess.PIPE, bufsize=(512 * 1024)) else: cmd = [ffmpeg_path, '-i', fname] + settings ffmpeg = subprocess.Popen(cmd, bufsize=(512 * 1024), stdout=subprocess.PIPE) if cmd: debug('transcoding to tivo model ' + tsn[:3] + ' using ffmpeg command:') debug(' '.join(cmd)) ffmpeg_procs[inFile] = { 'process': ffmpeg, 'start': 0, 'end': 0, 'last_read': time.time(), 'blocks': [] } if thead: ffmpeg_procs[inFile]['blocks'].append(thead) reap_process(inFile) return resume_transfer(inFile, outFile, 0)
def video_info(inFile, cache=True): vInfo = dict() fname = unicode(inFile, 'utf-8') mtime = os.path.getmtime(fname) if cache: if inFile in info_cache and info_cache[inFile][0] == mtime: debug('CACHE HIT! %s' % inFile) return info_cache[inFile][1] vInfo['Supported'] = True ffmpeg_path = config.get_bin('ffmpeg') if not ffmpeg_path: if os.path.splitext(inFile)[1].lower() not in [ '.mpg', '.mpeg', '.vob', '.tivo', '.ts' ]: vInfo['Supported'] = False vInfo.update({ 'millisecs': 0, 'vWidth': 704, 'vHeight': 480, 'rawmeta': {} }) if cache: info_cache[inFile] = (mtime, vInfo) return vInfo if mswindows: fname = fname.encode('cp1252') cmd = [ffmpeg_path, '-i', fname] # Windows and other OS buffer 4096 and ffmpeg can output more than that. err_tmp = tempfile.TemporaryFile() ffmpeg = subprocess.Popen(cmd, stderr=err_tmp, stdout=subprocess.PIPE, stdin=subprocess.PIPE) # wait configured # of seconds: if ffmpeg is not back give up limit = config.getFFmpegWait() if limit: for i in xrange(limit * 20): time.sleep(.05) if not ffmpeg.poll() == None: break if ffmpeg.poll() == None: kill(ffmpeg) vInfo['Supported'] = False if cache: info_cache[inFile] = (mtime, vInfo) return vInfo else: ffmpeg.wait() err_tmp.seek(0) output = err_tmp.read() err_tmp.close() debug('ffmpeg output=%s' % output) attrs = { 'container': r'Input #0, ([^,]+),', 'vCodec': r'Video: ([^, ]+)', # video codec 'aKbps': r'.*Audio: .+, (.+) (?:kb/s).*', # audio bitrate 'aCodec': r'.*Audio: ([^, ]+)', # audio codec 'aFreq': r'.*Audio: .+, (.+) (?:Hz).*', # audio frequency 'mapVideo': r'([0-9]+[.:]+[0-9]+).*: Video:.*' } # video mapping for attr in attrs: rezre = re.compile(attrs[attr]) x = rezre.search(output) if x: vInfo[attr] = x.group(1) else: if attr in ['container', 'vCodec']: vInfo[attr] = '' vInfo['Supported'] = False else: vInfo[attr] = None debug('failed at ' + attr) rezre = re.compile( r'.*Audio: .+, (?:(\d+)(?:(?:\.(\d).*)?(?: channels.*)?)|(stereo|mono)),.*' ) x = rezre.search(output) if x: if x.group(3): if x.group(3) == 'stereo': vInfo['aCh'] = 2 elif x.group(3) == 'mono': vInfo['aCh'] = 1 elif x.group(2): vInfo['aCh'] = int(x.group(1)) + int(x.group(2)) elif x.group(1): vInfo['aCh'] = int(x.group(1)) else: vInfo['aCh'] = None debug('failed at aCh') else: vInfo['aCh'] = None debug('failed at aCh') rezre = re.compile(r'.*Video: .+, (\d+)x(\d+)[, ].*') x = rezre.search(output) if x: vInfo['vWidth'] = int(x.group(1)) vInfo['vHeight'] = int(x.group(2)) else: vInfo['vWidth'] = '' vInfo['vHeight'] = '' vInfo['Supported'] = False debug('failed at vWidth/vHeight') rezre = re.compile(r'.*Video: .+, (.+) (?:fps|tb\(r\)|tbr).*') x = rezre.search(output) if x: vInfo['vFps'] = x.group(1) if '.' not in vInfo['vFps']: vInfo['vFps'] += '.00' # Allow override only if it is mpeg2 and frame rate was doubled # to 59.94 if vInfo['vCodec'] == 'mpeg2video' and vInfo['vFps'] != '29.97': # First look for the build 7215 version rezre = re.compile(r'.*film source: 29.97.*') x = rezre.search(output.lower()) if x: debug('film source: 29.97 setting vFps to 29.97') vInfo['vFps'] = '29.97' else: # for build 8047: rezre = re.compile(r'.*frame rate differs from container ' + r'frame rate: 29.97.*') debug('Bug in VideoReDo') x = rezre.search(output.lower()) if x: vInfo['vFps'] = '29.97' else: vInfo['vFps'] = '' vInfo['Supported'] = False debug('failed at vFps') durre = re.compile(r'.*Duration: ([0-9]+):([0-9]+):([0-9]+)\.([0-9]+),') d = durre.search(output) if d: vInfo['millisecs'] = ( (int(d.group(1)) * 3600 + int(d.group(2)) * 60 + int(d.group(3))) * 1000 + int(d.group(4)) * (10**(3 - len(d.group(4))))) else: vInfo['millisecs'] = 0 # get bitrate of source for tivo compatibility test. rezre = re.compile(r'.*bitrate: (.+) (?:kb/s).*') x = rezre.search(output) if x: vInfo['kbps'] = x.group(1) else: # Fallback method of getting video bitrate # Sample line: Stream #0.0[0x1e0]: Video: mpeg2video, yuv420p, # 720x480 [PAR 32:27 DAR 16:9], 9800 kb/s, 59.94 tb(r) rezre = re.compile(r'.*Stream #0\.0\[.*\]: Video: mpeg2video, ' + r'\S+, \S+ \[.*\], (\d+) (?:kb/s).*') x = rezre.search(output) if x: vInfo['kbps'] = x.group(1) else: vInfo['kbps'] = None debug('failed at kbps') # get par. rezre = re.compile(r'.*Video: .+PAR ([0-9]+):([0-9]+) DAR [0-9:]+.*') x = rezre.search(output) if x and x.group(1) != "0" and x.group(2) != "0": vInfo['par1'] = x.group(1) + ':' + x.group(2) vInfo['par2'] = float(x.group(1)) / float(x.group(2)) else: vInfo['par1'], vInfo['par2'] = None, None # get dar. rezre = re.compile(r'.*Video: .+DAR ([0-9]+):([0-9]+).*') x = rezre.search(output) if x and x.group(1) != "0" and x.group(2) != "0": vInfo['dar1'] = x.group(1) + ':' + x.group(2) else: vInfo['dar1'] = None # get Audio Stream mapping. rezre = re.compile(r'([0-9]+[.:]+[0-9]+)(.*): Audio:(.*)') x = rezre.search(output) amap = [] if x: for x in rezre.finditer(output): amap.append((x.group(1), x.group(2) + x.group(3))) else: amap.append(('', '')) debug('failed at mapAudio') vInfo['mapAudio'] = amap vInfo['par'] = None # get Metadata dump (newer ffmpeg). lines = output.split('\n') rawmeta = {} flag = False for line in lines: if line.startswith(' Metadata:'): flag = True else: if flag: if line.startswith(' Duration:'): flag = False else: try: key, value = [x.strip() for x in line.split(':', 1)] try: value = value.decode('utf-8') except: if sys.platform == 'darwin': value = value.decode('macroman') else: value = value.decode('cp1252') rawmeta[key] = [value] except: pass vInfo['rawmeta'] = rawmeta data = metadata.from_text(inFile) for key in data: if key.startswith('Override_'): vInfo['Supported'] = True if key.startswith('Override_mapAudio'): audiomap = dict(vInfo['mapAudio']) newmap = shlex.split(data[key]) audiomap.update(zip(newmap[::2], newmap[1::2])) vInfo['mapAudio'] = sorted(audiomap.items(), key=lambda (k, v): (k, v)) elif key.startswith('Override_millisecs'): vInfo[key.replace('Override_', '')] = int(data[key]) else: vInfo[key.replace('Override_', '')] = data[key] if cache: info_cache[inFile] = (mtime, vInfo) debug("; ".join(["%s=%s" % (k, v) for k, v in vInfo.items()])) return vInfo
def NPL(self, handler, query): global basic_meta shows_per_page = 50 # Change this to alter the number of shows returned folder = '' has_tivodecode = bool(config.get_bin('tivodecode')) if 'TiVo' in query: tivoIP = query['TiVo'][0] tsn = config.tivos_by_ip(tivoIP) tivo_name = config.tivo_names[tsn] tivo_mak = config.get_tsn('tivo_mak', tsn) theurl = ('https://' + tivoIP + '/TiVoConnect?Command=QueryContainer&ItemCount=' + str(shows_per_page) + '&Container=/NowPlaying') if 'Folder' in query: folder += query['Folder'][0] theurl += '/' + folder if 'AnchorItem' in query: theurl += '&AnchorItem=' + quote(query['AnchorItem'][0]) if 'AnchorOffset' in query: theurl += '&AnchorOffset=' + query['AnchorOffset'][0] if (theurl not in tivo_cache or (time.time() - tivo_cache[theurl]['thepage_time']) >= 60): # if page is not cached or old then retreive it auth_handler.add_password('TiVo DVR', tivoIP, 'tivo', tivo_mak) try: page = self.tivo_open(theurl) except IOError, e: handler.redir(UNABLE % tivoIP, 10) return tivo_cache[theurl] = { 'thepage': minidom.parse(page), 'thepage_time': time.time() } page.close() xmldoc = tivo_cache[theurl]['thepage'] items = xmldoc.getElementsByTagName('Item') TotalItems = tag_data(xmldoc, 'Details/TotalItems') ItemStart = tag_data(xmldoc, 'ItemStart') ItemCount = tag_data(xmldoc, 'ItemCount') FirstAnchor = tag_data(items[0], 'Links/Content/Url') data = [] for item in items: entry = {} entry['ContentType'] = tag_data(item, 'ContentType') for tag in ('CopyProtected', 'UniqueId'): value = tag_data(item, tag) if value: entry[tag] = value if entry['ContentType'] == 'x-tivo-container/folder': entry['Title'] = tag_data(item, 'Title') entry['TotalItems'] = tag_data(item, 'TotalItems') lc = tag_data(item, 'LastCaptureDate') if not lc: lc = tag_data(item, 'LastChangeDate') entry['LastChangeDate'] = time.strftime( '%b %d, %Y', time.localtime(int(lc, 16))) else: keys = { 'Icon': 'Links/CustomIcon/Url', 'Url': 'Links/Content/Url', 'SourceSize': 'Details/SourceSize', 'Duration': 'Details/Duration', 'CaptureDate': 'Details/CaptureDate' } for key in keys: value = tag_data(item, keys[key]) if value: entry[key] = value entry['SourceSize'] = ('%.3f GB' % (float(entry['SourceSize']) / (1024**3))) dur = int(entry['Duration']) / 1000 entry['Duration'] = ('%02d:%02d:%02d' % (dur / 3600, (dur % 3600) / 60, dur % 60)) entry['CaptureDate'] = time.strftime( '%b %d, %Y', time.localtime(int(entry['CaptureDate'], 16))) url = entry['Url'] if url in basic_meta: entry.update(basic_meta[url]) else: basic_data = metadata.from_container(item) entry.update(basic_data) basic_meta[url] = basic_data data.append(entry)
def QueryContainer(self, handler, query): def AudioFileFilter(f, filter_type=None): ext = os.path.splitext(f)[1].lower() if ext in ('.mp3', '.mp2') or (ext in TRANSCODE and config.get_bin('ffmpeg')): return self.AUDIO else: file_type = False if not filter_type or filter_type.split('/')[0] != self.AUDIO: if ext in PLAYLISTS: file_type = self.PLAYLIST elif os.path.isdir(f): file_type = self.DIRECTORY return file_type def media_data(f): if f.name in self.media_data_cache: return self.media_data_cache[f.name] item = {} item['path'] = f.name item['part_path'] = f.name.replace(local_base_path, '', 1) item['name'] = os.path.basename(f.name) item['is_dir'] = f.isdir item['is_playlist'] = f.isplay item['params'] = 'No' if f.title: item['Title'] = f.title if f.duration > 0: item['Duration'] = f.duration if f.isdir or f.isplay or '://' in f.name: self.media_data_cache[f.name] = item return item # If the format is: (track #) Song name... #artist, album, track = f.name.split(os.path.sep)[-3:] #track = os.path.splitext(track)[0] #if track[0].isdigit: # track = ' '.join(track.split(' ')[1:]) #item['SongTitle'] = track #item['AlbumTitle'] = album #item['ArtistName'] = artist ext = os.path.splitext(f.name)[1].lower() fname = unicode(f.name, 'utf-8') try: # If the file is an mp3, let's load the EasyID3 interface if ext == '.mp3': audioFile = MP3(fname, ID3=EasyID3) else: # Otherwise, let mutagen figure it out audioFile = mutagen.File(fname) if audioFile: # Pull the length from the FileType, if present if audioFile.info.length > 0: item['Duration'] = int(audioFile.info.length * 1000) # Grab our other tags, if present def get_tag(tagname, d): for tag in ([tagname] + TAGNAMES[tagname]): try: if tag in d: value = d[tag][0] if type(value) not in [str, unicode]: value = str(value) return value except: pass return '' artist = get_tag('artist', audioFile) title = get_tag('title', audioFile) if artist == 'Various Artists' and '/' in title: artist, title = [x.strip() for x in title.split('/')] item['ArtistName'] = artist item['SongTitle'] = title item['AlbumTitle'] = get_tag('album', audioFile) item['AlbumYear'] = get_tag('date', audioFile)[:4] item['MusicGenre'] = get_tag('genre', audioFile) except Exception, msg: print msg ffmpeg_path = config.get_bin('ffmpeg') if 'Duration' not in item and ffmpeg_path: if mswindows: fname = fname.encode('iso8859-1') cmd = [ffmpeg_path, '-i', fname] ffmpeg = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE, stdin=subprocess.PIPE) # wait 10 sec if ffmpeg is not back give up for i in xrange(200): time.sleep(.05) if not ffmpeg.poll() == None: break if ffmpeg.poll() != None: output = ffmpeg.stderr.read() d = durre(output) if d: millisecs = ((int(d.group(1)) * 3600 + int(d.group(2)) * 60 + int(d.group(3))) * 1000 + int(d.group(4)) * (10 ** (3 - len(d.group(4))))) else: millisecs = 0 item['Duration'] = millisecs if 'Duration' in item and ffmpeg_path: item['params'] = 'Yes' self.media_data_cache[f.name] = item return item
def video_info(inFile, cache=True): vInfo = dict() fname = unicode(inFile, 'utf-8') mtime = os.stat(fname).st_mtime if cache: if inFile in info_cache and info_cache[inFile][0] == mtime: debug('CACHE HIT! %s' % inFile) return info_cache[inFile][1] vInfo['Supported'] = True ffmpeg_path = config.get_bin('ffmpeg') if not ffmpeg_path: if os.path.splitext(inFile)[1].lower() not in ['.mpg', '.mpeg', '.vob', '.tivo']: vInfo['Supported'] = False vInfo.update({'millisecs': 0, 'vWidth': 704, 'vHeight': 480, 'rawmeta': {}}) if cache: info_cache[inFile] = (mtime, vInfo) return vInfo if mswindows: fname = fname.encode('iso8859-1') cmd = [ffmpeg_path, '-i', fname] # Windows and other OS buffer 4096 and ffmpeg can output more than that. err_tmp = tempfile.TemporaryFile() ffmpeg = subprocess.Popen(cmd, stderr=err_tmp, stdout=subprocess.PIPE, stdin=subprocess.PIPE) # wait configured # of seconds: if ffmpeg is not back give up limit = config.getFFmpegWait() if limit: for i in xrange(limit * 20): time.sleep(.05) if not ffmpeg.poll() == None: break if ffmpeg.poll() == None: kill(ffmpeg) vInfo['Supported'] = False if cache: info_cache[inFile] = (mtime, vInfo) return vInfo else: ffmpeg.wait() err_tmp.seek(0) output = err_tmp.read() err_tmp.close() debug('ffmpeg output=%s' % output) attrs = {'container': r'Input #0, ([^,]+),', 'vCodec': r'Video: ([^, ]+)', # video codec 'aKbps': r'.*Audio: .+, (.+) (?:kb/s).*', # audio bitrate 'aCodec': r'.*Audio: ([^, ]+)', # audio codec 'aFreq': r'.*Audio: .+, (.+) (?:Hz).*', # audio frequency 'mapVideo': r'([0-9]+[.:]+[0-9]+).*: Video:.*'} # video mapping for attr in attrs: rezre = re.compile(attrs[attr]) x = rezre.search(output) if x: vInfo[attr] = x.group(1) else: if attr in ['container', 'vCodec']: vInfo[attr] = '' vInfo['Supported'] = False else: vInfo[attr] = None debug('failed at ' + attr) rezre = re.compile(r'.*Audio: .+, (?:(\d+)(?:(?:\.(\d).*)?(?: channels.*)?)|(stereo|mono)),.*') x = rezre.search(output) if x: if x.group(3): if x.group(3) == 'stereo': vInfo['aCh'] = 2 elif x.group(3) == 'mono': vInfo['aCh'] = 1 elif x.group(2): vInfo['aCh'] = int(x.group(1)) + int(x.group(2)) elif x.group(1): vInfo['aCh'] = int(x.group(1)) else: vInfo['aCh'] = None debug('failed at aCh') else: vInfo['aCh'] = None debug('failed at aCh') rezre = re.compile(r'.*Video: .+, (\d+)x(\d+)[, ].*') x = rezre.search(output) if x: vInfo['vWidth'] = int(x.group(1)) vInfo['vHeight'] = int(x.group(2)) else: vInfo['vWidth'] = '' vInfo['vHeight'] = '' vInfo['Supported'] = False debug('failed at vWidth/vHeight') rezre = re.compile(r'.*Video: .+, (.+) (?:fps|tb\(r\)|tbr).*') x = rezre.search(output) if x: vInfo['vFps'] = x.group(1) if '.' not in vInfo['vFps']: vInfo['vFps'] += '.00' # Allow override only if it is mpeg2 and frame rate was doubled # to 59.94 if vInfo['vCodec'] == 'mpeg2video' and vInfo['vFps'] != '29.97': # First look for the build 7215 version rezre = re.compile(r'.*film source: 29.97.*') x = rezre.search(output.lower()) if x: debug('film source: 29.97 setting vFps to 29.97') vInfo['vFps'] = '29.97' else: # for build 8047: rezre = re.compile(r'.*frame rate differs from container ' + r'frame rate: 29.97.*') debug('Bug in VideoReDo') x = rezre.search(output.lower()) if x: vInfo['vFps'] = '29.97' else: vInfo['vFps'] = '' vInfo['Supported'] = False debug('failed at vFps') durre = re.compile(r'.*Duration: ([0-9]+):([0-9]+):([0-9]+)\.([0-9]+),') d = durre.search(output) if d: vInfo['millisecs'] = ((int(d.group(1)) * 3600 + int(d.group(2)) * 60 + int(d.group(3))) * 1000 + int(d.group(4)) * (10 ** (3 - len(d.group(4))))) else: vInfo['millisecs'] = 0 # get bitrate of source for tivo compatibility test. rezre = re.compile(r'.*bitrate: (.+) (?:kb/s).*') x = rezre.search(output) if x: vInfo['kbps'] = x.group(1) else: # Fallback method of getting video bitrate # Sample line: Stream #0.0[0x1e0]: Video: mpeg2video, yuv420p, # 720x480 [PAR 32:27 DAR 16:9], 9800 kb/s, 59.94 tb(r) rezre = re.compile(r'.*Stream #0\.0\[.*\]: Video: mpeg2video, ' + r'\S+, \S+ \[.*\], (\d+) (?:kb/s).*') x = rezre.search(output) if x: vInfo['kbps'] = x.group(1) else: vInfo['kbps'] = None debug('failed at kbps') # get par. rezre = re.compile(r'.*Video: .+PAR ([0-9]+):([0-9]+) DAR [0-9:]+.*') x = rezre.search(output) if x and x.group(1) != "0" and x.group(2) != "0": vInfo['par1'] = x.group(1) + ':' + x.group(2) vInfo['par2'] = float(x.group(1)) / float(x.group(2)) else: vInfo['par1'], vInfo['par2'] = None, None # get dar. rezre = re.compile(r'.*Video: .+DAR ([0-9]+):([0-9]+).*') x = rezre.search(output) if x and x.group(1) != "0" and x.group(2) != "0": vInfo['dar1'] = x.group(1) + ':' + x.group(2) else: vInfo['dar1'] = None # get Audio Stream mapping. rezre = re.compile(r'([0-9]+[.:]+[0-9]+)(.*): Audio:(.*)') x = rezre.search(output) amap = [] if x: for x in rezre.finditer(output): amap.append((x.group(1), x.group(2) + x.group(3))) else: amap.append(('', '')) debug('failed at mapAudio') vInfo['mapAudio'] = amap vInfo['par'] = None # get Metadata dump (newer ffmpeg). lines = output.split('\n') rawmeta = {} flag = False for line in lines: if line.startswith(' Metadata:'): flag = True else: if flag: if line.startswith(' Duration:'): flag = False else: try: key, value = [x.strip() for x in line.split(':', 1)] try: value = value.decode('utf-8') except: if sys.platform == 'darwin': value = value.decode('macroman') else: value = value.decode('iso8859-1') rawmeta[key] = [value] except: pass vInfo['rawmeta'] = rawmeta data = metadata.from_text(inFile) for key in data: if key.startswith('Override_'): vInfo['Supported'] = True if key.startswith('Override_mapAudio'): audiomap = dict(vInfo['mapAudio']) newmap = shlex.split(data[key]) audiomap.update(zip(newmap[::2], newmap[1::2])) vInfo['mapAudio'] = sorted(audiomap.items(), key=lambda (k,v): (k,v)) elif key.startswith('Override_millisecs'): vInfo[key.replace('Override_', '')] = int(data[key]) else: vInfo[key.replace('Override_', '')] = data[key] if cache: info_cache[inFile] = (mtime, vInfo) debug("; ".join(["%s=%s" % (k, v) for k, v in vInfo.items()])) return vInfo
def send_file(self, handler, path, query): mime = 'video/x-tivo-mpeg' tsn = handler.headers.getheader('tsn', '') try: assert (tsn) tivo_name = config.tivos[tsn].get('name', tsn) except: tivo_name = handler.address_string() is_tivo_file = (path[-5:].lower() == '.tivo') if 'Format' in query: mime = query['Format'][0] needs_tivodecode = (is_tivo_file and mime == 'video/mpeg') compatible = (not needs_tivodecode and transcode.tivo_compatible(path, tsn, mime)[0]) try: # "bytes=XXX-" offset = int(handler.headers.getheader('Range')[6:-1]) except: offset = 0 if needs_tivodecode: valid = bool( config.get_bin('tivodecode') and config.get_server('tivo_mak')) else: valid = True if valid and offset: valid = ((compatible and offset < os.path.getsize(path)) or (not compatible and transcode.is_resumable(path, offset))) #faking = (mime in ['video/x-tivo-mpeg-ts', 'video/x-tivo-mpeg'] and faking = (mime == 'video/x-tivo-mpeg' and not (is_tivo_file and compatible)) fname = unicode(path, 'utf-8') thead = '' if faking: thead = self.tivo_header(tsn, path, mime) if compatible: size = os.path.getsize(fname) + len(thead) handler.send_response(200) handler.send_header('Content-Length', size - offset) handler.send_header( 'Content-Range', 'bytes %d-%d/%d' % (offset, size - offset - 1, size)) else: handler.send_response(206) handler.send_header('Transfer-Encoding', 'chunked') handler.send_header('Content-Type', mime) handler.end_headers() logger.info('[%s] Start sending "%s" to %s' % (time.strftime('%d/%b/%Y %H:%M:%S'), fname, tivo_name)) start = time.time() count = 0 if valid: if compatible: if faking and not offset: handler.wfile.write(thead) logger.debug('"%s" is tivo compatible' % fname) f = open(fname, 'rb') try: if mime == 'video/mp4': count = qtfaststart.process(f, handler.wfile, offset) else: if offset: offset -= len(thead) f.seek(offset) while True: block = f.read(512 * 1024) if not block: break handler.wfile.write(block) count += len(block) except Exception, msg: logger.info(msg) f.close() else: logger.debug('"%s" is not tivo compatible' % fname) if offset: count = transcode.resume_transfer(path, handler.wfile, offset) else: count = transcode.transcode(False, path, handler.wfile, tsn, mime, thead)
HTML_CONTAINER_TEMPLATE_MOBILE = tmpl('container_mob.tmpl') HTML_CONTAINER_TEMPLATE = tmpl('container_html.tmpl') XML_CONTAINER_TEMPLATE = tmpl('container_xml.tmpl') TVBUS_TEMPLATE = tmpl('TvBus.tmpl') EXTENSIONS = """.tivo .mpg .avi .wmv .mov .flv .f4v .vob .mp4 .m4v .mkv .ts .tp .trp .3g2 .3gp .3gp2 .3gpp .amv .asf .avs .bik .bix .box .bsf .dat .dif .divx .dmb .dpg .dv .dvr-ms .evo .eye .flc .fli .flx .gvi .ivf .m1v .m21 .m2t .m2ts .m2v .m2p .m4e .mjp .mjpeg .mod .moov .movie .mp21 .mpe .mpeg .mpv .mpv2 .mqv .mts .mvb .nsv .nuv .nut .ogm .qt .rm .rmvb .rts .scm .smv .ssm .svi .vdo .vfw .vid .viv .vivo .vp6 .vp7 .vro .webm .wm .wmd .wtv .yuv""".split() use_extensions = True try: assert(config.get_bin('ffmpeg')) except: use_extensions = False queue = [] # Recordings to push def uniso(iso): return time.strptime(iso[:19], '%Y-%m-%dT%H:%M:%S') def isodt(iso): return datetime(*uniso(iso)[:6]) def isogm(iso): return int(calendar.timegm(uniso(iso))) class Pushable(object):
HTML_CONTAINER_TEMPLATE = tmpl('container_html.tmpl') XML_CONTAINER_TEMPLATE = tmpl('container_xml.tmpl') TVBUS_TEMPLATE = tmpl('TvBus.tmpl') EXTENSIONS = """.tivo .mpg .avi .wmv .mov .flv .f4v .vob .mp4 .m4v .mkv .ts .tp .trp .3g2 .3gp .3gp2 .3gpp .amv .asf .avs .bik .bix .box .bsf .dat .dif .divx .dmb .dpg .dv .dvr-ms .evo .eye .flc .fli .flx .gvi .ivf .m1v .m21 .m2t .m2ts .m2v .m2p .m4e .mjp .mjpeg .mod .moov .movie .mp21 .mpe .mpeg .mpv .mpv2 .mqv .mts .mvb .nsv .nuv .nut .ogm .qt .rm .rmvb .rts .scm .smv .ssm .svi .vdo .vfw .vid .viv .vivo .vp6 .vp7 .vro .webm .wm .wmd .wtv .yuv""".split() use_extensions = True try: assert (config.get_bin('ffmpeg')) except: use_extensions = False queue = [] # Recordings to push def uniso(iso): return time.strptime(iso[:19], '%Y-%m-%dT%H:%M:%S') def isodt(iso): return datetime(*uniso(iso)[:6]) def isogm(iso):
def QueryContainer(self, handler, query): def AudioFileFilter(f, filter_type=None): ext = os.path.splitext(f)[1].lower() if ext in ('.mp3', '.mp2') or (ext in TRANSCODE and config.get_bin('ffmpeg')): return self.AUDIO else: file_type = False if not filter_type or filter_type.split('/')[0] != self.AUDIO: if ext in PLAYLISTS: file_type = self.PLAYLIST elif os.path.isdir(f): file_type = self.DIRECTORY return file_type def media_data(f): if f.name in self.media_data_cache: return self.media_data_cache[f.name] item = {} item['path'] = f.name item['part_path'] = f.name.replace(local_base_path, '', 1) item['name'] = os.path.basename(f.name) item['is_dir'] = f.isdir item['is_playlist'] = f.isplay item['params'] = 'No' if f.title: item['Title'] = f.title if f.duration > 0: item['Duration'] = f.duration if f.isdir or f.isplay or '://' in f.name: self.media_data_cache[f.name] = item return item # If the format is: (track #) Song name... #artist, album, track = f.name.split(os.path.sep)[-3:] #track = os.path.splitext(track)[0] #if track[0].isdigit: # track = ' '.join(track.split(' ')[1:]) #item['SongTitle'] = track #item['AlbumTitle'] = album #item['ArtistName'] = artist ext = os.path.splitext(f.name)[1].lower() fname = unicode(f.name, 'utf-8') try: # If the file is an mp3, let's load the EasyID3 interface if ext == '.mp3': audioFile = MP3(fname, ID3=EasyID3) else: # Otherwise, let mutagen figure it out audioFile = mutagen.File(fname) if audioFile: # Pull the length from the FileType, if present if audioFile.info.length > 0: item['Duration'] = int(audioFile.info.length * 1000) # Grab our other tags, if present def get_tag(tagname, d): for tag in ([tagname] + TAGNAMES[tagname]): try: if tag in d: value = d[tag][0] if type(value) not in [str, unicode]: value = str(value) return value except: pass return '' artist = get_tag('artist', audioFile) title = get_tag('title', audioFile) if artist == 'Various Artists' and '/' in title: artist, title = [x.strip() for x in title.split('/')] item['ArtistName'] = artist item['SongTitle'] = title item['AlbumTitle'] = get_tag('album', audioFile) item['AlbumYear'] = get_tag('date', audioFile)[:4] item['MusicGenre'] = get_tag('genre', audioFile) except Exception, msg: print msg ffmpeg_path = config.get_bin('ffmpeg') if 'Duration' not in item and ffmpeg_path: if mswindows: fname = fname.encode('iso8859-1') cmd = [ffmpeg_path, '-i', fname] ffmpeg = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE, stdin=subprocess.PIPE) # wait 10 sec if ffmpeg is not back give up for i in xrange(200): time.sleep(.05) if not ffmpeg.poll() == None: break if ffmpeg.poll() != None: output = ffmpeg.stderr.read() d = durre(output) if d: millisecs = ( (int(d.group(1)) * 3600 + int(d.group(2)) * 60 + int(d.group(3))) * 1000 + int(d.group(4)) * (10**(3 - len(d.group(4))))) else: millisecs = 0 item['Duration'] = millisecs if 'Duration' in item and ffmpeg_path: item['params'] = 'Yes' self.media_data_cache[f.name] = item return item
def media_data(f): if f.name in self.media_data_cache: return self.media_data_cache[f.name] item = {} item['path'] = f.name item['part_path'] = f.name.replace(local_base_path, '', 1) item['name'] = os.path.basename(f.name) item['is_dir'] = f.isdir item['is_playlist'] = f.isplay item['params'] = 'No' if f.title: item['Title'] = f.title if f.duration > 0: item['Duration'] = f.duration if f.isdir or f.isplay or '://' in f.name: self.media_data_cache[f.name] = item return item # If the format is: (track #) Song name... #artist, album, track = f.name.split(os.path.sep)[-3:] #track = os.path.splitext(track)[0] #if track[0].isdigit: # track = ' '.join(track.split(' ')[1:]) #item['SongTitle'] = track #item['AlbumTitle'] = album #item['ArtistName'] = artist ext = os.path.splitext(f.name)[1].lower() fname = f.name try: # If the file is an mp3, let's load the EasyID3 interface if ext == '.mp3': audioFile = MP3(fname, ID3=EasyID3) else: # Otherwise, let mutagen figure it out audioFile = mutagen.File(fname) if audioFile: # Pull the length from the FileType, if present if audioFile.info.length > 0: item['Duration'] = int(audioFile.info.length * 1000) # Grab our other tags, if present def get_tag(tagname, d): for tag in ([tagname] + TAGNAMES[tagname]): try: if tag in d: value = d[tag][0] if not isinstance(value, str): value = str(value) return value except: pass return '' artist = get_tag('artist', audioFile) title = get_tag('title', audioFile) if artist == 'Various Artists' and '/' in title: artist, title = [x.strip() for x in title.split('/')] item['ArtistName'] = artist item['SongTitle'] = title item['AlbumTitle'] = get_tag('album', audioFile) item['AlbumYear'] = get_tag('date', audioFile)[:4] item['MusicGenre'] = get_tag('genre', audioFile) except Exception as msg: logger.error(msg) ffmpeg_path = config.get_bin('ffmpeg') if 'Duration' not in item and ffmpeg_path: cmd = [ffmpeg_path, '-hide_banner', '-nostdin', '-i', fname] ffmpeg = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE) # wait 10 sec if ffmpeg is not back give up for i in range(200): time.sleep(.05) if not ffmpeg.poll() == None: break if ffmpeg.poll() != None: output = ffmpeg.stderr.read() d = durre(output) if d: millisecs = ((int(d.group(1)) * 3600 + int(d.group(2)) * 60 + int(d.group(3))) * 1000 + int(d.group(4)) * (10 ** (3 - len(d.group(4))))) else: millisecs = 0 item['Duration'] = millisecs if 'Duration' in item and ffmpeg_path: item['params'] = 'Yes' self.media_data_cache[f.name] = item return item
except Exception, msg: status[url]['running'] = False status[url]['error'] = str(msg) return tivo_name = config.tivos[config.tivos_by_ip(tivoIP)].get( 'name', tivoIP) logger.info('[%s] Start getting "%s" from %s' % (time.strftime('%d/%b/%Y %H:%M:%S'), outfile, tivo_name)) if status[url]['decode']: fname = outfile if mswindows: fname = fname.encode('cp1252') tivodecode_path = config.get_bin('tivodecode') tcmd = [tivodecode_path, '-m', mak, '-o', fname, '-'] tivodecode = subprocess.Popen(tcmd, stdin=subprocess.PIPE, bufsize=(512 * 1024)) f = tivodecode.stdin else: f = open(outfile, 'wb') length = 0 start_time = time.time() last_interval = start_time now = start_time try: while status[url]['running']: output = handle.read(1024000) if not output:
def NPL(self, handler, query): def getint(thing): try: result = int(thing) except: result = 0 return result global basic_meta global details_urls shows_per_page = 50 # Change this to alter the number of shows returned folder = '' FirstAnchor = '' has_tivodecode = bool(config.get_bin('tivodecode')) if 'TiVo' in query: tivoIP = query['TiVo'][0] tsn = config.tivos_by_ip(tivoIP) attrs = config.tivos[tsn] tivo_name = attrs.get('name', tivoIP) tivo_mak = config.get_tsn('tivo_mak', tsn) protocol = attrs.get('protocol', 'https') ip_port = '%s:%d' % (tivoIP, attrs.get('port', 443)) path = attrs.get('path', DEFPATH) baseurl = '%s://%s%s' % (protocol, ip_port, path) theurl = baseurl if 'Folder' in query: folder = query['Folder'][0] theurl = urlparse.urljoin(theurl, folder) theurl += '&ItemCount=%d' % shows_per_page if 'AnchorItem' in query: theurl += '&AnchorItem=' + quote(query['AnchorItem'][0]) if 'AnchorOffset' in query: theurl += '&AnchorOffset=' + query['AnchorOffset'][0] if (theurl not in tivo_cache or (time.time() - tivo_cache[theurl]['thepage_time']) >= 60): # if page is not cached or old then retreive it auth_handler.add_password('TiVo DVR', ip_port, 'tivo', tivo_mak) try: page = self.tivo_open(theurl) except IOError, e: handler.redir(UNABLE % (tivoIP, cgi.escape(str(e))), 10) return tivo_cache[theurl] = { 'thepage': minidom.parse(page), 'thepage_time': time.time() } page.close() xmldoc = tivo_cache[theurl]['thepage'] items = xmldoc.getElementsByTagName('Item') TotalItems = tag_data(xmldoc, 'TiVoContainer/Details/TotalItems') ItemStart = tag_data(xmldoc, 'TiVoContainer/ItemStart') ItemCount = tag_data(xmldoc, 'TiVoContainer/ItemCount') title = tag_data(xmldoc, 'TiVoContainer/Details/Title') if items: FirstAnchor = tag_data(items[0], 'Links/Content/Url') data = [] for item in items: entry = {} for tag in ('CopyProtected', 'ContentType'): value = tag_data(item, 'Details/' + tag) if value: entry[tag] = value if entry['ContentType'].startswith('x-tivo-container'): entry['Url'] = tag_data(item, 'Links/Content/Url') entry['Title'] = tag_data(item, 'Details/Title') entry['TotalItems'] = tag_data(item, 'Details/TotalItems') lc = tag_data(item, 'Details/LastCaptureDate') if not lc: lc = tag_data(item, 'Details/LastChangeDate') entry['LastChangeDate'] = time.strftime( '%b %d, %Y', time.localtime(int(lc, 16))) else: keys = { 'Icon': 'Links/CustomIcon/Url', 'Url': 'Links/Content/Url', 'Details': 'Links/TiVoVideoDetails/Url', 'SourceSize': 'Details/SourceSize', 'Duration': 'Details/Duration', 'CaptureDate': 'Details/CaptureDate' } for key in keys: value = tag_data(item, keys[key]) if value: entry[key] = value if 'SourceSize' in entry: rawsize = entry['SourceSize'] entry['SourceSize'] = metadata.human_size(rawsize) if 'Duration' in entry: dur = getint(entry['Duration']) / 1000 entry['Duration'] = ('%d:%02d:%02d' % (dur / 3600, (dur % 3600) / 60, dur % 60)) if 'CaptureDate' in entry: entry['CaptureDate'] = time.strftime( '%b %d, %Y', time.localtime(int(entry['CaptureDate'], 16))) url = urlparse.urljoin(baseurl, entry['Url']) entry['Url'] = url if url in basic_meta: entry.update(basic_meta[url]) else: basic_data = metadata.from_container(item) entry.update(basic_data) basic_meta[url] = basic_data if 'Details' in entry: details_urls[url] = entry['Details'] data.append(entry)