Beispiel #1
0
def ffmpeg_concat_rel_speed(filenames, filenames_file, output_file, rel_speed,
                            fps):
    # Auto name FIRST_to_LAST
    if output_file is None:
        first_date = str2dt(filenames[0]).date()
        last_date = str2dt(filenames[-1]).date()
        output_file = first_date.isoformat() + "_to_" + last_date.isoformat()

    if rel_speed == 1:
        cl = "ffmpeg -hide_banner -y -f concat -safe 0 -i " + \
            filenames_file + " -c copy " + f"-r {fps} {output_file}"
    else:
        # output_file += "_xoutput_file" + "{0:.1f}".format(rel_speed)
        factor = 1 / rel_speed
        cl = "ffmpeg -hide_banner -y -f concat -safe 0 -i " + filenames_file + " -preset veryfast -filter:v setpts=" + str(
            factor) + "*PTS " + f"-r {fps} {output_file}"
    the_call = cl.split(" ")
    # use abs path to easily report errors directing user to log
    log_filename = os.path.abspath(os.path.basename(output_file) + ".ffmpeg")
    p_log = open(log_filename, "w")
    p_log.write("called: {}\n".format(
        ' '.join(the_call)))  # ignored? overwriten
    LOGGER.debug("calling: {}\n".format(' '.join(the_call)))

    r = subprocess.call(the_call, stdout=p_log, stderr=subprocess.STDOUT)
    if r == 0:
        #  print(output_file)
        unlink_safe(log_filename)
    else:
        raise RuntimeError(
            "Failed on ffmpeg concat. Check log:{} Called:'{}' Return:{}".
            format(log_filename, ' '.join(the_call), r))
Beispiel #2
0
def test_rename_console(setup_module):
    """ Rename three files with only EXIF data """
    for f in glob.glob(str(TEST_DATA / "rename" / "*.*", )):
        p = Path(f)
        shutil.copyfile(f, p.name)

    cl = ["rename", "--log-level", "DEBUG", "*.*"]
    with pytest.raises(SystemExit):
        image_tools_console(cl)

    renamed = list(glob.glob("*"))
    renamed.sort()
    assert len(renamed) == 3
    assert str2dt(renamed[0]) == dt(2019, 3, 30, 17, 40, 48)
    assert str2dt(renamed[1]) == dt(2019, 3, 30, 18, 4, 6)
    assert str2dt(renamed[2]) == dt(2020, 12, 25, 19, 22, 28)
Beispiel #3
0
 def __init__(self, filename, real_start_time, real_interval=None, real_end_time=None, real_date=None):
     super().__init__(filename)
     if (real_interval and real_end_time) or (not real_interval and not real_end_time):
         raise RuntimeError("Use real_interval or real_end_time, not both")
     self._real_start_time = real_start_time
     self._real_interval = real_interval
     self._real_end_time = real_end_time
     if not real_date:
         self._real_date = str2dt(Path(filename).stem).date()
Beispiel #4
0
def find_matching_files(date_list, video_files):
    matches = []
    for d in date_list:
        try:
            # match if filename starts with the date
            match = next(video for video in video_files
                         if d == str2dt(video).date())
            matches.append(match)
        except BaseException:
            LOGGER.info("No video for {}".format(d))  # not found

    return matches
Beispiel #5
0
def video_join(src_videos: list, dest_video: str, start_datetime, end_datetime,
               speed_rel, fps):

    LOGGER.debug("Searching {} to {}".format(start_datetime.isoformat(),
                                             end_datetime.isoformat()))
    invalid_videos = []
    for v in src_videos:
        if not valid(v) or str2dt(v, throw=False) is None:
            invalid_videos.append(v)

    if len(invalid_videos) > 0:
        LOGGER.warning("Ignoring {} invalid video(s) : {}".format(
            len(invalid_videos), invalid_videos))

    video_files = list(set(src_videos) - set(invalid_videos))

    video_files_in_range = [
        v for v in video_files
        if start_datetime.date() <= str2dt(v).date() <= end_datetime.date()
    ]

    if len(video_files_in_range) < 1:
        raise VideoMakerError("No videos found to concat")

    video_files_in_range.sort(
    )  # probably redundant as ls returns in alphabetical order
    # logger.info("concat'ing: {}".format('\n'.join(video_files_in_range)))
    videos_filename = 'filelist.txt'
    with open(videos_filename, 'w') as f:
        f.write("# Auto-generated\n")
        for video in video_files_in_range:
            f.write("file '%s'\n" % str(video))

    ffmpeg_concat_rel_speed(video_files_in_range, videos_filename, dest_video,
                            speed_rel, fps)
    unlink_safe(videos_filename)
Beispiel #6
0
    def read_image_times(self):
        self.images = []
        n_errors = 0
        LOGGER.debug(f"Reading dates of {len(self._file_list)} files...")
        LOGGER.debug(
            f"start: {self.start} end: {self.end} start_time:{self.start_time} end_time:{self.end_time}"
        )

        if not self._file_list:
            raise VideoMakerError("No image files found in command-line")

        for fn in self._file_list:
            # Using second resolution can lead to *variable* intervals. For instance, if the interval is 4.1s,
            # the durations with be 4/300 (0.0133) but then each 10 frames 5/300
            # It's therefore better to use constant frame rate, or to adjust this function
            # to millisecond resolution and/or round
            try:
                datetime_taken = str2dt(fn)
                if (self.end_time >= datetime_taken.time() >= self.start_time
                        and self.end >= datetime_taken >= self.start):
                    tlf = TLFile(fn, datetime_taken)
                    if self.validate_images:
                        if tlf.valid():
                            self.images.append(tlf)
                        else:
                            raise ImageError("Invalid image")
                    else:
                        self.images.append(tlf)
            except ImageError as exc:
                n_errors += 1
                LOGGER.warning(f"Ignoring {Path(fn).absolute()}: {exc}")
            except Exception as exc:
                n_errors += 1
                LOGGER.warning(
                    f"Ignoring exception getting datetime of {Path(fn).absolute()}: {exc}"
                )

        LOGGER.info(
            f"Got images for {len(self.images)}/{len(self._file_list)} files, and {n_errors} failures"
        )
        if n_errors:
            LOGGER.warning(
                f"No dates available for {n_errors}/{len(self._file_list)} files. Ignoring them."
            )
        return self.images.sort()
Beispiel #7
0
def extract_dated_images(filename, output, start_time=None, end_time=None, interval=None, ocr=False):
    """
     Read a video, check metadata to understand real time and then extract images into dated files
     """
    if start_time:
        vi = ManualTimes(filename, real_start_time=start_time, real_interval=interval, real_end_time=end_time)
    else:
        vi = VideoInfo(filename)
    the_call = ["ffmpeg", "-hide_banner", "-loglevel", "verbose", "-y"]  # -y : overwrite
    the_call.extend(["-i", filename])
    # frame_pts is new and unavailable - use real_timestamps instead of:
    # the_call.extend(['-frame_pts', 'true'])
    the_call.extend(['-qscale:v', '2'])  # jpeg quality: 2-5 is good : https://stackoverflow.com/questions/10225403/how-can-i-extract-a-good-quality-jpeg-image-from-an-h264-video-file-with-ffmpeg
    the_call.extend(['%06d.jpg'])
    run_and_capture(the_call)  # throw on fail
    rx = re.compile(r'\d\d\d\d\d\d\.jpg')  # glob can't match this properly
    image_filenames = [f for f in Path(".").glob("*.jpg") if rx.match(str(f)) is not None]
    last_ts = vi.real_start
    try:
        for f in sorted(image_filenames):
            if ocr:
                im = Image.open(f)
                im_iv = ImageOps.grayscale(im)
                im_iv = ImageOps.invert(im_iv)
                im_iv = im_iv.crop((50, im.height - 100, 300, im.height))
                im_iv.save("invert.jpg")
                text = image_to_string(im_iv, config="digits")
                text = image_to_string(im_iv, lang='eng', config="-c tessedit_char_whitelist=0123456789 -oem 0")
                ts = str2dt(text, throw=False) or (last_ts + interval)
                LOGGER.debug(f"file: {f} text:{text} ts:{ts}")
                raise NotImplementedError("tesseract cannot see digits")
            else:
                ts = vi.real_timestamps[int(f.stem) - 1]

            day_dir = Path(output) / Path(dt2str(ts.date()))
            day_dir.mkdir(exist_ok=True)
            new_filename = dt2str(ts) + f.suffix
            new_path = day_dir / new_filename
            LOGGER.debug(f"file: {f} ts:{ts} new:{new_path}")
            shutil.move(str(f), str(new_path))
            last_ts = ts
    except KeyError as exc:
        KeyError(f"{exc}: cannot find metadata in {filename}?")
Beispiel #8
0
def video_join_console():
    parser = argparse.ArgumentParser("Combine timelapse videos")
    parser.add_argument("start",
                        "-s",
                        type=lambda s: parse(s, ignoretz=True),
                        default=dt.min,
                        help="eg. \"2 days ago\", 2000-01-20T16:00:00")
    parser.add_argument("end",
                        "-e",
                        type=lambda s: parse(s, ignoretz=True),
                        default=dt.max,
                        help="eg. Today, 2000-01-20T16:00:00")
    parser.add_argument('--log-level',
                        '-ll',
                        default='WARNING',
                        type=lambda s: LOG_LEVELS(s).name,
                        nargs='?',
                        choices=LOG_LEVELS.choices())
    parser.add_argument("input_movies",
                        nargs="+",
                        help="All possible movie files to search")
    parser.add_argument(
        "--output",
        help=
        "Force the output filename, instead of automatically assigned based on dates."
    )
    speed_group = parser.add_mutually_exclusive_group()
    speed_group.add_argument(
        "--speed-rel",
        default=1,
        help=
        "Relative speed multipler. e.g. 1 is no change, 2 is twice as fast, 0.5 is twice as slow."
    )
    speed_group.add_argument("--speed-abs",
                             default=None,
                             help="Absolute speed (real time / video time)")

    args = (parser.parse_args())

    try:
        logging.basicConfig(format=LOG_FORMAT)
        LOGGER.setLevel(args.log_level)
        if args.start is None or args.end is None:
            raise SyntaxError("Couldn't understand dates: {}, {}".format(
                args.start, args.end))
        LOGGER.debug("Searching {} to {}".format(args.start.isoformat(),
                                                 args.end.isoformat()))

        video_files = args.input_movies
        invalid_videos = []
        for v in video_files:
            if not valid(v) or str2dt(v, throw=False) is None:
                invalid_videos.append(v)

        if len(invalid_videos) > 0:
            LOGGER.warning("Ignoring {} invalid videos: {}".format(
                len(invalid_videos), invalid_videos))

        video_files = list(set(video_files) - set(invalid_videos))

        video_files_in_range = [
            v for v in video_files
            if args.start.date() <= str2dt(v).date() <= args.end.date()
        ]

        if len(video_files_in_range) < 1:
            LOGGER.debug(
                f"No videos found. video_files: {','.join(video_files)}")
            raise RuntimeError("No videos found to concat")
        video_files_in_range.sort(
        )  # probably redundant as ls returns in alphabetical order
        LOGGER.debug("concat'ing: {}".format('\n'.join(video_files_in_range)))
        videos_filename = 'filelist.txt'
        with open(videos_filename, 'w') as f:
            f.write("# Auto-generated by TMV\n")
            for video in video_files_in_range:
                f.write("file '%s'\n" % str(video))

        if args.speed_abs is not None:
            raise NotImplementedError
            # ffmpeg_concat_abs_speed(
            #  videos_filename, args.output, float(args.speed_abs))
        else:
            ffmpeg_concat_rel_speed(video_files_in_range, videos_filename,
                                    args.output, float(args.speed_rel), 25)
        unlink_safe(videos_filename)
        sys.exit(0)
    except Exception as exc:
        print(exc, file=sys.stderr)
        LOGGER.error(exc)
        LOGGER.debug(f"Exception: {exc}", exc_info=exc)
        sys.exit(1)
Beispiel #9
0
 def real_start(self) -> dt:
     description = self.info_dict['format']['tags']['description']  # couple with videod.py!
     dt_str, _ = description.split(",")
     return str2dt(dt_str)