Exemple #1
0
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)
Exemple #2
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
Exemple #3
0
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
Exemple #4
0
    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
Exemple #5
0
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
Exemple #6
0
    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]
Exemple #7
0
    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")
Exemple #8
0
    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))
Exemple #9
0
    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)))
Exemple #10
0
    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
Exemple #11
0
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
Exemple #12
0
    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
Exemple #13
0
    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)