Beispiel #1
0
def select_audioch(inFile: str, tsn: str) -> List[str]:
    # AC-3 max channels is 5.1
    vInfo = video_info(inFile)
    if vInfo.aCh is not None and vInfo.aCh > 6:
        LOGGER.debug("Too many audio channels for AC-3, using 5.1 instead")
        return ["-ac", "6"]
    return []
Beispiel #2
0
def supported_format(inFile: str) -> bool:
    vInfo = video_info(inFile)
    if vInfo.Supported:
        return True
    else:
        LOGGER.debug("FALSE, file not supported %s" % inFile)
        return False
Beispiel #3
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
Beispiel #4
0
def select_audiofr(inFile: str, tsn: str) -> List[str]:
    freq = "48000"  # default
    vInfo = video_info(inFile)
    if vInfo.aFreq == "44100":
        # compatible frequency
        freq = vInfo.aFreq
    return ["-ar", freq]
Beispiel #5
0
def select_audiocodec(isQuery: bool,
                      inFile: str,
                      tsn: str = "",
                      mime: str = "") -> List[str]:
    if inFile[-5:].lower() == ".tivo":
        return ["-c:a", "copy"]
    vInfo = video_info(inFile)
    codectype = vInfo.vCodec
    # Default, compatible with all TiVo's
    codec = "ac3"
    compatiblecodecs = ("ac3", "liba52", "mp2")

    if vInfo.aCodec in compatiblecodecs:
        aKbps = vInfo.aKbps
        aCh = vInfo.aCh
        if aKbps is None:
            if not isQuery:
                vInfoQuery = audio_check(inFile, tsn)
                if vInfoQuery is None:
                    aKbps = None
                    aCh = None
                else:
                    aKbps = vInfoQuery.aKbps
                    aCh = vInfoQuery.aCh
            else:
                codec = "TBA"
        if aKbps and aKbps <= getMaxAudioBR(tsn):
            # compatible codec and bitrate, do not reencode audio
            codec = "copy"
        if vInfo.aCodec != "ac3" and (aCh is None or aCh > 2):
            codec = "ac3"
    val = ["-c:a", codec]
    if not (codec == "copy" and codectype == "mpeg2video"):
        val.append("-copyts")
    return val
Beispiel #6
0
def select_videostr(inFile: str, tsn: str, mime: str = "") -> int:
    vInfo = video_info(inFile)
    if tivo_compatible_video(vInfo, tsn, mime)[0] and vInfo.kbps is not None:
        video_str = vInfo.kbps
        if vInfo.aKbps is not None:
            video_str -= vInfo.aKbps
        video_str *= 1000
    else:
        video_str = strtod(getVideoBR(tsn))
        if isHDtivo(tsn) and vInfo.kbps:
            video_str = max(video_str, vInfo.kbps * 1000)
        video_str = int(min(strtod(getMaxVideoBR(tsn)) * 0.95, video_str))
    return video_str
Beispiel #7
0
def select_videocodec(inFile: str, tsn: str, mime: str = "") -> List[str]:
    codec = ["-c:v"]
    vInfo = video_info(inFile)
    if tivo_compatible_video(vInfo, tsn, mime)[0]:
        codec.append("copy")
        if mime == "video/x-tivo-mpeg-ts":
            org_codec = vInfo.vCodec
            if org_codec == "h264":
                codec += ["-bsf:v", "h264_mp4toannexb", "-muxdelay", "0"]
            elif org_codec == "hevc":
                codec += ["-bsf:v", "hevc_mp4toannexb"]
    else:
        codec += ["mpeg2video", "-pix_fmt", "yuv420p"]  # default
    return codec
Beispiel #8
0
def select_audiolang(inFile: str, tsn: str) -> str:
    vInfo = video_info(inFile)
    audio_lang = get_tsn("audio_lang", tsn)
    LOGGER.debug("audio_lang: %s" % audio_lang)
    if vInfo.mapAudio:
        # default to first detected audio stream to begin with
        stream = vInfo.mapAudio[0][0]
        LOGGER.debug("set first detected audio stream by default: %s" % stream)
    # TODO: why do we check mapVideo in the following?
    if (audio_lang is not None and vInfo.mapAudio is not None
            and vInfo.mapVideo is not None):
        langmatch_curr = []
        langmatch_prev = vInfo.mapAudio[:]
        for lang in audio_lang.replace(" ", "").lower().split(","):
            LOGGER.debug("matching lang: %s" % lang)
            for s, l in langmatch_prev:
                if lang in s + l.replace(" ", "").lower():
                    LOGGER.debug("matched: %s" % s +
                                 l.replace(" ", "").lower())
                    langmatch_curr.append((s, l))
            # if only 1 item matched we're done
            if len(langmatch_curr) == 1:
                stream = langmatch_curr[0][0]
                LOGGER.debug("found exactly one match: %s" % stream)
                break
            # if more than 1 item matched copy the curr area to the prev
            # array we only need to look at the new shorter list from
            # now on
            elif len(langmatch_curr) > 1:
                langmatch_prev = langmatch_curr[:]
                # default to the first item matched thus far
                stream = langmatch_curr[0][0]
                LOGGER.debug("remember first match: %s" % stream)
                langmatch_curr = []
    # don't let FFmpeg auto select audio stream, pyTivo defaults to
    # first detected
    if stream and vInfo.mapVideo is not None:
        LOGGER.debug("selected audio stream: %s" % stream)
        return "-map " + vInfo.mapVideo + " -map " + stream
    # if no audio is found
    LOGGER.debug("selected audio stream: None detected")
    return ""
Beispiel #9
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
Beispiel #10
0
    def metadata_full(
        self,
        full_path: str,
        tsn: str = "",
        mime: str = "",
        mtime: Optional[float] = None,
    ) -> Dict[str, Any]:
        data: Dict[str, Any] = {}
        vInfo = video_info(full_path)

        if vInfo.vHeight is None or vInfo.vWidth is None:
            LOGGER.error("vInfo.vHeight or vInfo.vWidth is None")
            return data

        if (vInfo.vHeight >= 720 and getTivoHeight(tsn) >= 720) or (
                vInfo.vWidth >= 1280 and getTivoWidth(tsn) >= 1280):
            data["showingBits"] = "4096"

        data.update(basic(full_path, mtime))
        if full_path[-5:].lower() == ".tivo":
            data.update(from_tivo(full_path))
        if full_path[-4:].lower() == ".wtv":
            data.update(from_mscore(vInfo.rawmeta))

        if "episodeNumber" in data:
            try:
                ep = int(data["episodeNumber"])
            except:
                ep = 0
            data["episodeNumber"] = str(ep)

        if getDebug() and "vHost" not in data:
            compatible, reason = tivo_compatible(full_path, tsn, mime)
            if compatible:
                transcode_options: List[str] = []
            else:
                transcode_options = transcode_settings(True, full_path, tsn,
                                                       mime)
            data["vHost"] = ([
                "TRANSCODE=%s, %s" % (["YES", "NO"][compatible], reason)
            ] + ["SOURCE INFO: "] + [
                "%s=%s" % (k, v)
                for k, v in sorted(list(vInfo._asdict().items()), reverse=True)
            ] + ["TRANSCODE OPTIONS: "] + transcode_options +
                             ["SOURCE FILE: ",
                              os.path.basename(full_path)])

        now = datetime.utcnow()
        if "time" in data:
            if data["time"].lower() == "file":
                if not mtime:
                    mtime = os.path.getmtime(full_path)
                try:
                    now = datetime.utcfromtimestamp(mtime)
                except:
                    LOGGER.warning("Bad file time on " + full_path)
            elif data["time"].lower() == "oad":
                now = isodt(data["originalAirDate"])
            else:
                try:
                    now = isodt(data["time"])
                except:
                    LOGGER.warning("Bad time format: " + data["time"] +
                                   " , using current time")

        duration = self.__duration(full_path)
        if duration is None:
            LOGGER.error("duration is None")
            return data

        duration_delta = timedelta(milliseconds=duration)
        min = duration_delta.seconds / 60
        sec = duration_delta.seconds % 60
        hours = min / 60
        min = min % 60

        data.update({
            "time":
            now.isoformat(),
            "startTime":
            now.isoformat(),
            "stopTime": (now + duration_delta).isoformat(),
            "size":
            self.__est_size(full_path, tsn, mime),
            "duration":
            duration,
            "iso_duration":
            ("P%sDT%sH%sM%sS" % (duration_delta.days, hours, min, sec)),
        })

        return data
Beispiel #11
0
 def __duration(self, full_path: str) -> Optional[float]:
     return video_info(full_path).millisecs
Beispiel #12
0
def select_aspect(inFile: str, tsn: str = "") -> List[str]:
    tivo_width = getTivoWidth(tsn)
    tivo_height = getTivoHeight(tsn)

    vInfo = video_info(inFile)

    LOGGER.debug("tsn: %s" % tsn)

    aspect169 = get169Setting(tsn)

    LOGGER.debug("aspect169: %s" % aspect169)

    optres = getOptres(tsn)

    LOGGER.debug("optres: %s" % optres)

    if vInfo.vHeight is None or vInfo.vWidth is None:
        LOGGER.error(
            "Internal Error: vInfo.vHeight is None or vInfo.vHeight is None.")
        return []

    if optres:
        optHeight = nearestTivoHeight(vInfo.vHeight)
        optWidth = nearestTivoWidth(vInfo.vWidth)
        if optHeight < tivo_height:
            tivo_height = optHeight
        if optWidth < tivo_width:
            tivo_width = optWidth

    if vInfo.par2:
        par2 = vInfo.par2
    elif vInfo.par:
        par2 = float(vInfo.par)
    else:
        # Assume PAR = 1.0
        par2 = 1.0

    LOGGER.debug(
        ("File=%s vCodec=%s vWidth=%s vHeight=%s vFps=%s millisecs=%s " +
         "tivo_height=%s tivo_width=%s") % (
             inFile,
             vInfo.vCodec,
             vInfo.vWidth,
             vInfo.vHeight,
             vInfo.vFps,
             vInfo.millisecs,
             tivo_height,
             tivo_width,
         ))

    if isHDtivo(tsn) and not optres:
        if vInfo.par:
            npar = par2

            # adjust for pixel aspect ratio, if set

            if npar < 1.0:
                return [
                    "-s",
                    "%dx%d" % (vInfo.vWidth, math.ceil(vInfo.vHeight / npar))
                ]
            elif npar > 1.0:
                # FFMPEG expects width to be a multiple of two
                return [
                    "-s",
                    "%dx%d" %
                    (math.ceil(vInfo.vWidth * npar / 2.0) * 2, vInfo.vHeight),
                ]

        if vInfo.vHeight <= tivo_height:
            # pass all resolutions to S3, except heights greater than
            # conf height
            return []
        # else, resize video.

    d = gcd(vInfo.vHeight, vInfo.vWidth)
    rheight, rwidth = vInfo.vHeight / d, vInfo.vWidth / d
    LOGGER.debug("rheight=%s rwidth=%s" % (rheight, rwidth))

    if (rwidth, rheight) in [(1, 1)] and vInfo.par1 == "8:9":
        LOGGER.debug("File + PAR is within 4:3.")
        return ["-aspect", "4:3", "-s", "%sx%s" % (tivo_width, tivo_height)]

    elif (rwidth, rheight) in [
        (4, 3),
        (10, 11),
        (15, 11),
        (59, 54),
        (59, 72),
        (59, 36),
        (59, 54),
    ] or vInfo.dar1 == "4:3":
        LOGGER.debug("File is within 4:3 list.")
        return ["-aspect", "4:3", "-s", "%sx%s" % (tivo_width, tivo_height)]

    elif ((rwidth, rheight) in [(16, 9), (20, 11), (40, 33), (118, 81),
                                (59, 27)]
          or vInfo.dar1 == "16:9") and (aspect169 or get169Letterbox(tsn)):
        LOGGER.debug("File is within 16:9 list and 16:9 allowed.")

        if get169Blacklist(tsn) or (aspect169 and get169Letterbox(tsn)):
            aspect = "4:3"
        else:
            aspect = "16:9"
        return ["-aspect", aspect, "-s", "%sx%s" % (tivo_width, tivo_height)]

    else:
        settings = ["-aspect"]

        multiplier16by9 = (16.0 * tivo_height) / (9.0 * tivo_width) / par2
        multiplier4by3 = (4.0 * tivo_height) / (3.0 * tivo_width) / par2
        ratio = vInfo.vWidth * 100 * par2 / vInfo.vHeight
        LOGGER.debug("par2=%.3f ratio=%.3f mult4by3=%.3f" %
                     (par2, ratio, multiplier4by3))

        # If video is wider than 4:3 add top and bottom padding

        if ratio > 133:  # Might be 16:9 file, or just need padding on
            # top and bottom

            if aspect169 and ratio > 135:  # If file would fall in 4:3
                # assume it is supposed to be 4:3

                if get169Blacklist(tsn) or get169Letterbox(tsn):
                    settings.append("4:3")
                else:
                    settings.append("16:9")

                if ratio > 177:  # too short needs padding top and bottom
                    settings += pad_TB(tivo_width, tivo_height,
                                       multiplier16by9, vInfo)
                    LOGGER.debug(("16:9 aspect allowed, file is wider " +
                                  "than 16:9 padding top and bottom\n%s") %
                                 " ".join(settings))

                else:  # too skinny needs padding on left and right.
                    settings += pad_LR(tivo_width, tivo_height,
                                       multiplier16by9, vInfo)
                    LOGGER.debug(("16:9 aspect allowed, file is narrower " +
                                  "than 16:9 padding left and right\n%s") %
                                 " ".join(settings))

            else:  # this is a 4:3 file or 16:9 output not allowed
                if ratio > 135 and get169Letterbox(tsn):
                    settings.append("16:9")
                    multiplier = multiplier16by9
                else:
                    settings.append("4:3")
                    multiplier = multiplier4by3
                settings += pad_TB(tivo_width, tivo_height, multiplier, vInfo)
                LOGGER.debug(
                    ("File is wider than 4:3 padding " + "top and bottom\n%s")
                    % " ".join(settings))

        # If video is taller than 4:3 add left and right padding, this
        # is rare. All of these files will always be sent in an aspect
        # ratio of 4:3 since they are so narrow.

        else:
            settings.append("4:3")
            settings += pad_LR(tivo_width, tivo_height, multiplier4by3, vInfo)
            LOGGER.debug("File is taller than 4:3 padding left and right\n%s" %
                         " ".join(settings))

        return settings
Beispiel #13
0
def select_videofps(inFile: str, tsn: str) -> List[str]:
    vInfo = video_info(inFile)
    fps = ["-r", "29.97"]  # default
    if isHDtivo(tsn) and vInfo.vFps in GOOD_MPEG_FPS:
        fps = []
    return fps