def transcode(inFile: str, outFile: BinaryIO, tsn: str = "", mime: str = "", thead: bytes = b"") -> int: settings = transcode_settings(isQuery=False, inFile=inFile, tsn=tsn, mime=mime) ffmpeg_path = get_bin("ffmpeg") if ffmpeg_path is None: LOGGER.error("No ffmpeg binary found") return 0 if inFile[-5:].lower() == ".tivo": tivodecode_path = get_bin("tivodecode") if tivodecode_path is None: LOGGER.error("No tivodecode binary found.") return 0 tivo_mak = get_server("tivo_mak", "") if tivo_mak == "": LOGGER.error("No valid tivo_mak found.") return 0 tcmd = [tivodecode_path, "-m", tivo_mak, inFile] 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", inFile] + settings ffmpeg = subprocess.Popen(cmd, bufsize=(512 * 1024), stdout=subprocess.PIPE) if cmd: LOGGER.debug("transcoding to tivo model " + tsn[:3] + " using ffmpeg command:") LOGGER.debug(" ".join(cmd)) FFMPEG_PROCS[inFile] = FfmpegProcess(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 tivo_compatible(inFile: str, tsn: str = "", mime: str = "") -> Tuple[bool, str]: vInfo = video_info(inFile) message = (True, "all compatible") if not 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 LOGGER.debug("TRANSCODE=%s, %s, %s" % (["YES", "NO"][message[0]], message[1], inFile)) return message
def from_tivo(full_path: str) -> Dict[str, str]: tdcat_path = get_bin("tdcat") tivo_mak = 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) except: metadata = {} return metadata
def AudioFileFilter(self, f: str, filter_type: Optional[str] = None) -> Union[bool, str]: ext = os.path.splitext(f)[1].lower() file_type: Union[bool, str] if ext in (".mp3", ".mp2") or (ext in TRANSCODE and get_bin("ffmpeg")): return self.AUDIO else: file_type = False if filter_type is None 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 audio_check(inFile: str, tsn: str) -> Optional[VideoInfo]: 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 -") ffmpeg_bin = get_bin("ffmpeg") if ffmpeg_bin is None: LOGGER.error("Can't locate ffmpeg binary.") return None cmd = [ffmpeg_bin, "-i", inFile] + 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 get_tivo_file(self, tivoIP: str, url: str, mak: str, togo_path: str) -> None: # global STATUS STATUS[url].update({"running": True, "queued": False}) parse_url = urllib.parse.urlparse(url) name_list = unquote(parse_url[2]).split("/")[-1].split(".") try: id = unquote(parse_url[4]).split("id=")[1] name_list.insert(-1, " - " + id) except: pass ts = STATUS[url]["ts_format"] if STATUS[url]["decode"]: if ts: name_list[-1] = "ts" else: name_list[-1] = "mpg" else: if ts: name_list.insert(-1, " (TS)") else: name_list.insert(-1, " (PS)") name_list.insert(-1, ".") name: str = "".join(name) for ch in BADCHAR: name = name.replace(ch, BADCHAR[ch]) outfile = os.path.join(togo_path, name) if STATUS[url]["save"]: meta = BASIC_META[url] try: handle = self.tivo_open(DETAILS_URLS[url]) meta.update(from_details(handle.read().decode("utf-8"))) handle.close() except: pass metafile = open(outfile + ".txt", "w") dump(metafile, meta) metafile.close() AUTH_HANDLER.add_password("TiVo DVR", url, "tivo", mak) try: if STATUS[url]["ts_format"]: handle = self.tivo_open(url + "&Format=video/x-tivo-mpeg-ts") else: handle = self.tivo_open(url) except Exception as msg: STATUS[url]["running"] = False STATUS[url]["error"] = str(msg) return tivo_name = pytivo.config.TIVOS[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"]: tivodecode_path = get_bin("tivodecode") if tivodecode_path is None: STATUS[url]["running"] = False STATUS[url]["error"] = "Can't find binary 'tivodecode'." return 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 length += len(output) f.write(output) now = time.time() elapsed = now - last_interval if elapsed >= 5: STATUS[url]["rate"] = "%.2f Mb/s" % ( length * 8.0 / (elapsed * 1024 * 1024)) STATUS[url]["size"] += length length = 0 last_interval = now if STATUS[url]["running"]: STATUS[url]["finished"] = True except Exception as msg: STATUS[url]["running"] = False LOGGER.info(msg) handle.close() f.close() STATUS[url]["size"] += length if STATUS[url]["running"]: mega_elapsed = (now - start_time) * 1024 * 1024 if mega_elapsed < 1: mega_elapsed = 1 size = STATUS[url]["size"] rate = size * 8.0 / mega_elapsed LOGGER.info('[%s] Done getting "%s" from %s, %d bytes, %.2f Mb/s' % (time.strftime("%d/%b/%Y %H:%M:%S"), outfile, tivo_name, size, rate)) STATUS[url]["running"] = False else: os.remove(outfile) if STATUS[url]["save"]: os.remove(outfile + ".txt") LOGGER.info( '[%s] Transfer of "%s" from %s aborted' % (time.strftime("%d/%b/%Y %H:%M:%S"), outfile, tivo_name)) del STATUS[url]
def NPL(self, handler: "TivoHTTPHandler", query: Query) -> None: global BASIC_META global DETAILS_URLS shows_per_page = 50 # Change this to alter the number of shows returned folder = "" FirstAnchor = "" has_tivodecode = bool(get_bin("tivodecode")) if "TiVo" in query: tivoIP = query["TiVo"][0] tsn = tivos_by_ip(tivoIP) attrs = pytivo.config.TIVOS[tsn] tivo_name = attrs.get("name", tivoIP) tivo_mak = get_tsn("tivo_mak", tsn) if tivo_mak is None: handler.redir("Unable to find Tivo.", 10) return 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 = urllib.parse.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 as 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 = int( tag_data(xmldoc, "TiVoContainer/Details/TotalItems")) ItemStart = int(tag_data(xmldoc, "TiVoContainer/ItemStart")) ItemCount = int(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"] = 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 = urllib.parse.urljoin(baseurl, entry["Url"]) entry["Url"] = url if url in BASIC_META: entry.update(BASIC_META[url]) else: basic_data = from_container(item) entry.update(basic_data) BASIC_META[url] = basic_data if "Details" in entry: DETAILS_URLS[url] = entry["Details"] data.append(entry) else: data = [] tivoIP = "" TotalItems = 0 ItemStart = 0 ItemCount = 0 title = "" t = NPL_TCLASS() t.quote = quote t.folder = folder t.status = STATUS if tivoIP in QUEUE: t.queue = QUEUE[tivoIP] t.has_tivodecode = has_tivodecode t.togo_mpegts = is_ts_capable(tsn) t.tname = tivo_name t.tivoIP = tivoIP t.container = handler.cname t.data = data t.len = len t.TotalItems = getint(TotalItems) t.ItemStart = getint(ItemStart) t.ItemCount = getint(ItemCount) t.FirstAnchor = quote(FirstAnchor) t.shows_per_page = shows_per_page t.title = title handler.send_html(str(t), refresh="300")
def send_file(self, handler: "TivoHTTPHandler", path: str, query: Query) -> None: mime = "video/x-tivo-mpeg" tsn = handler.headers.get("tsn", "") try: assert tsn tivo_name = pytivo.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 tivo_compatible(path, tsn, mime)[0] try: # "bytes=XXX-" offset = int(handler.headers.get("Range")[6:-1]) except: offset = 0 if needs_tivodecode: valid = bool(get_bin("tivodecode") and get_server("tivo_mak", "")) else: valid = True if valid and offset: valid = (compatible and offset < os.path.getsize(path)) or ( not compatible and 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) thead = b"" if faking: thead = self.tivo_header(tsn, path, mime) if compatible: size = os.path.getsize(path) + len(thead) handler.send_response(200) handler.send_header("Content-Length", str(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"), path, 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' % path) f = open(path, "rb") try: 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 as msg: LOGGER.info(msg) f.close() else: LOGGER.debug('"%s" is not tivo compatible' % path) if offset: count = resume_transfer(path, handler.wfile, offset) else: count = transcode(path, handler.wfile, tsn, mime, thead) try: if not compatible: handler.wfile.write(b"0\r\n\r\n") handler.wfile.flush() except Exception as msg: LOGGER.info(msg) mega_elapsed = (time.time() - start) * 1024 * 1024 if mega_elapsed < 1: mega_elapsed = 1 rate = count * 8.0 / mega_elapsed LOGGER.info( '[%s] Done sending "%s" to %s, %d bytes, %.2f Mb/s' % (time.strftime("%d/%b/%Y %H:%M:%S"), path, tivo_name, count, rate))
file=os.path.join(SCRIPTDIR, "templates", "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() LIKELYTS = """.ts .tp .trp .3g2 .3gp .3gp2 .3gpp .m2t .m2ts .mts .mp4 .m4v .flv .mkv .mov .wtv .dvr-ms .webm""".split() use_extensions = True try: assert get_bin("ffmpeg") except: use_extensions = False def uniso(iso: str) -> time.struct_time: return time.strptime(iso[:19], "%Y-%m-%dT%H:%M:%S") def isodt(iso: str) -> datetime: return datetime(*uniso(iso)[:6]) def isogm(iso: str) -> int: return int(calendar.timegm(uniso(iso)))
def get_image_ffmpeg( self, path: str, width: int, height: int, pshape: str, rot: int, attrs: Dict[str, Any], ) -> Tuple[bool, bytes]: ffmpeg_path = get_bin("ffmpeg") if not ffmpeg_path: return False, b"FFmpeg not found" if attrs and "size" in attrs: result = attrs["size"] else: status, result = self.get_size_ffmpeg(ffmpeg_path, path) 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", path, "-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 = getFFmpegWait() if limit: for i in range(limit * 20): time.sleep(0.05) if not ffmpeg.poll() is None: break if ffmpeg.poll() is None: kill(ffmpeg) return False, b"FFmpeg timed out" else: ffmpeg.wait() jpeg_tmp.seek(0) output = jpeg_tmp.read() jpeg_tmp.close() if b"JFIF" not in output[:10]: output = output[:2] + JFIF_TAG + output[2:] return True, output
def video_info(inFile: str, cache: bool = True) -> VideoInfo: vInfo: Dict[str, Any] = {} mtime = os.path.getmtime(inFile) if cache: if inFile in INFO_CACHE and INFO_CACHE[inFile][0] == mtime: LOGGER.debug("CACHE HIT! %s" % inFile) return INFO_CACHE[inFile][1] vInfo["Supported"] = True ffmpeg_path = get_bin("ffmpeg") if ffmpeg_path is None: 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": {} }) vid_info = VideoInfo(**vInfo) if cache: INFO_CACHE[inFile] = (mtime, vid_info) return vid_info cmd = [ffmpeg_path, "-i", inFile] # 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 = getFFmpegWait() if limit: for i in range(limit * 20): time.sleep(0.05) if not ffmpeg.poll() is None: break if ffmpeg.poll() is None: kill(ffmpeg) vInfo["Supported"] = False vid_info = VideoInfo(**vInfo) if cache: INFO_CACHE[inFile] = (mtime, vid_info) return vid_info else: ffmpeg.wait() err_tmp.seek(0) output = err_tmp.read().decode("utf-8") err_tmp.close() LOGGER.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: if attr in ["aKbps"]: vInfo[attr] = int(x.group(1)) else: vInfo[attr] = x.group(1) else: if attr in ["container", "vCodec"]: vInfo[attr] = "" vInfo["Supported"] = False else: vInfo[attr] = None LOGGER.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 LOGGER.debug("failed at aCh") else: vInfo["aCh"] = None LOGGER.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"] = None vInfo["vHeight"] = None vInfo["Supported"] = False LOGGER.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: LOGGER.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.*") LOGGER.debug("Bug in VideoReDo") x = rezre.search(output.lower()) if x: vInfo["vFps"] = "29.97" else: vInfo["vFps"] = "" vInfo["Supported"] = False LOGGER.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"] = int(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"] = int(x.group(1)) else: vInfo["kbps"] = None LOGGER.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(("", "")) LOGGER.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)] rawmeta[key] = [value] except: pass vInfo["rawmeta"] = rawmeta data = 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(list(zip(newmap[::2], newmap[1::2]))) vInfo["mapAudio"] = sorted(list(audiomap.items()), key=lambda k_v: (k_v[0], k_v[1])) 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) LOGGER.debug("; ".join(["%s=%s" % (k, v) for k, v in list(vInfo.items())])) vid_info = VideoInfo(**vInfo) if cache: INFO_CACHE[inFile] = (mtime, vid_info) return vid_info
def media_data(self, f: FileDataMusic, local_base_path: str) -> Dict[str, Any]: if f.name in self.media_data_cache: return self.media_data_cache[f.name] item: Dict[str, Any] = {} 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() try: # If the file is an mp3, let's load the EasyID3 interface if ext == ".mp3": audioFile = MP3(f.name, ID3=EasyID3) else: # Otherwise, let mutagen figure it out audioFile = mutagen.File(f.name) 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 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: print(msg) ffmpeg_path = get_bin("ffmpeg") if "Duration" not in item and ffmpeg_path: cmd = [ffmpeg_path, "-i", f.name] 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 range(200): time.sleep(0.05) if ffmpeg.poll() is not None: break if ffmpeg.poll() is not None: output = ffmpeg.stderr.read() d = durre(output.decode("utf-8")) 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 send_file(self, handler: "TivoHTTPHandler", path: str, query: Query) -> None: seek = int(query.get("Seek", ["0"])[0]) duration = int(query.get("Duration", ["0"])[0]) always = handler.container.getboolean("force_ffmpeg") and get_bin( "ffmpeg") ext = os.path.splitext(path)[1].lower() needs_transcode = ext in TRANSCODE or seek or duration or always if not needs_transcode: fsize = os.path.getsize(path) handler.send_response(200) handler.send_header("Content-Length", str(fsize)) else: if get_bin("ffmpeg") is None: LOGGER.error("ffmpeg is not found. Aborting transcode.") return handler.send_response(206) handler.send_header("Transfer-Encoding", "chunked") handler.send_header("Content-Type", "audio/mpeg") handler.end_headers() if needs_transcode: cmd: List[str] cmd = [get_bin("ffmpeg"), "-i", path, "-vn"] # type: ignore 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(b"%x\r\n" % len(block)) handler.wfile.write(block) handler.wfile.write(b"\r\n") except Exception as msg: LOGGER.info(msg) kill(ffmpeg) break if not block: break else: f = open(path, "rb") try: shutil.copyfileobj(f, handler.wfile) except: pass f.close() try: handler.wfile.flush() except Exception as msg: LOGGER.info(msg)