Skip to content

mvdate Modules

Move files based on date.

construct_date_time(file_creation, nesting=None, single=False)

Construct date and time from a file creation date.

Parameters:

Name Type Description Default
file_creation float

Date and time of file creation.

required
nesting str

Level of nesting to extract, values are 'Y'ear, 'm'onth, 'd'ay, 'H'our, 'M'inutes. Defaults to 'd'ay if not specified.

None
single bool

Whether to make target directory a single rather than nested structure.

False

Returns:

Type Description
str

Directory structure to create with given level of nesting.

Source code in mvdate/mvdate.py
def construct_date_time(file_creation: float | None, nesting: str | None = None, single: bool = False) -> str:
    """
    Construct date and time from a file creation date.

    Parameters
    ----------
    file_creation : float
        Date and time of file creation.
    nesting : str
        Level of nesting to extract, values are 'Y'ear, 'm'onth, 'd'ay, 'H'our, 'M'inutes. Defaults to 'd'ay if not
        specified.
    single : bool
        Whether to make target directory a single rather than nested structure.

    Returns
    -------
    str
        Directory structure to create with given level of nesting.
    """
    local_file_creation = time.localtime(file_creation)
    sep = "-" if single else "/"
    if nesting == "Y":
        return time.strftime("%Y", local_file_creation)
    if nesting == "m":
        return time.strftime(f"%Y{sep}%m", local_file_creation)
    if nesting == "d":
        return time.strftime(f"%Y{sep}%m{sep}%d", local_file_creation)
    if nesting == "H":
        return time.strftime(f"%Y{sep}%m{sep}%d{sep}%H", local_file_creation)
    if nesting == "M":
        return time.strftime(f"%Y{sep}%m{sep}%d{sep}%H{sep}%M", local_file_creation)
    return time.strftime(f"%Y{sep}%m{sep}%d", local_file_creation)

create_parser()

Create a parser for reading options.

Returns:

Type Description
ArgumentParser

Returns an argument parser.

Source code in mvdate/mvdate.py
def create_parser() -> arg.ArgumentParser:
    """
    Create a parser for reading options.

    Returns
    -------
    arg.ArgumentParser
        Returns an argument parser.
    """
    parser = arg.ArgumentParser(description="Move files to directory structure based on files date.")
    parser.add_argument(
        "-v",
        "--version",
        action="version",
        version=f"Installed version of mvdate : {__version__}",
        help="Report the current installed version of mvdate.",
    )
    parser.add_argument("-e", "--ext", dest="ext", required=False, default="jpg", help="File extension to search for.")
    parser.add_argument(
        "-b",
        "--base",
        dest="base",
        type=Path,
        required=False,
        default="./",
        help="Base directory to search for files with extension type. Default './",
    )
    parser.add_argument(
        "-d",
        "--destination",
        dest="destination",
        type=Path,
        required=False,
        default="./",
        help="Destination directory to   move files to. Default './",
    )
    parser.add_argument(
        "-l", "--log_file", dest="log_file", type=str, required=False, default=None, help="File to log output to."
    )
    parser.add_argument(
        "-m",
        "--method",
        dest="method",
        required=False,
        default="exif",
        help="Method to extract the files creation date based on 'ctime', 'mtime' or 'exif' (default).",
    )
    parser.add_argument(
        "-n",
        "--nesting",
        dest="nesting",
        type=str,
        required=False,
        default="d",
        help="Structure of target directory, 'Y'ear, 'm'onth, 'd'ay (default), 'H'our, 'M'inutes.",
    )
    parser.add_argument(
        "-q",
        "--quiet",
        dest="quiet",
        action="store_true",
        required=False,
        default=False,
        help="Execute quietly and suppress all output.",
    )
    parser.add_argument(
        "-s",
        "--single",
        dest="single",
        required=False,
        default=False,
        help="Whether to have a single directory of the form 'YYYY-mm[-dd[-HH[-MM]]]' rather than a nested structure.",
    )
    return parser

create_target_dir(date_time, destination='./', quiet=True)

Create the target directory.

Parameters:

Name Type Description Default
date_time str

Date/time construct to be created within the destination directory.

required
destination str | Path

Path where target directory structure is to be created.

'./'
quiet bool

Report creation of target directory.

True

Returns:

Type Description
None

Does not return anything, simply creates the target directory.

Source code in mvdate/mvdate.py
def create_target_dir(date_time: str, destination: str | Path = "./", quiet: bool = True) -> None:
    """
    Create the target directory.

    Parameters
    ----------
    date_time : str
        Date/time construct to be created within the destination directory.
    destination : str | Path
        Path where target directory structure is to be created.
    quiet : bool
        Report creation of target directory.

    Returns
    -------
    None
        Does not return anything, simply creates the target directory.
    """
    destination = Path(destination) / date_time
    destination.mkdir(parents=True, exist_ok=True)
    if not quiet:
        logger.info(f"Created target directory : {destination}")

find(base='./', ext='jpg')

Find files of a given type.

Parameters:

Name Type Description Default
base str | Path

Directory to search for files.

'./'
ext str

File extension to search for.

'jpg'

Returns:

Type Description
list

List of found files.

Source code in mvdate/mvdate.py
def find(base: str | Path = "./", ext: str = "jpg") -> list[Path]:
    """
    Find files of a given type.

    Parameters
    ----------
    base : str | Path
        Directory to search for files.
    ext : str
        File extension to search for.

    Returns
    -------
    list
        List of found files.
    """
    return list(Path(base).rglob(f"*.{ext}"))

get_file_date(file, method='exif')

Extract created date from file.

Parameters:

Name Type Description Default
file Path

File to extract created date from.

required
method str

Date/time extraction method to use, currently supports exif (default), ctime and mtime.

'exif'

Returns:

Type Description
float

Returns the date as an elapsed float from origin.

Source code in mvdate/mvdate.py
def get_file_date(file: Path, method: str = "exif") -> float | None:
    """
    Extract created date from file.

    Parameters
    ----------
    file : Path
        File to extract created date from.
    method : str
        Date/time extraction method to use, currently supports exif (default), ctime and mtime.

    Returns
    -------
    float
        Returns the date as an elapsed float from origin.
    """
    file_date = _get_file_date(method)
    return file_date(file)  # type: ignore[no-any-return]

main(args=None)

Find and move files.

Parameters:

Name Type Description Default
args ArgumentParser

Arguments to run the function with.

None
Source code in mvdate/mvdate.py
def main(args: arg.ArgumentParser | None = None) -> None:
    """
    Find and move files.

    Parameters
    ----------
    args : arg.ArgumentParser
        Arguments to run the function with.
    """
    parser = create_parser()
    args = parser.parse_args() if args is None else parser.parse_args(args)  # type: ignore[call-overload, assignment]
    arguments = vars(args)
    # Find files
    files_to_move = find(base=Path(arguments["base"]), ext=arguments["ext"])
    if not arguments["quiet"]:
        f = Figlet(font="slant")
        print(f.renderText("mvdate"))
        logger.info(f"Search directory                           : {arguments['base']}")
        logger.info(f"Searching for files with extension         : {arguments['ext']}")
        logger.info(f"Files found                                : {len(files_to_move)}")
        logger.info(f"Destination directory                      : {arguments['destination']}")
    # Extract all file dates
    all_file_dates = [get_file_date(x, method=arguments["method"]) for x in list(files_to_move)]
    # Extract target directories, making a unique set
    target_date_times = [
        construct_date_time(x, nesting=arguments["nesting"], single=arguments["single"]) for x in all_file_dates
    ]
    target_date_time_unique = set(target_date_times)
    # Create target directories
    for date_time in target_date_time_unique:
        create_target_dir(
            date_time, arguments["destination"], arguments["quiet"]
        )  # pylint: disable=expression-not-assigned
    # Move files
    for file_to_move, all_target_dir in tqdm(
        zip(list(files_to_move), target_date_times, strict=True), desc=f"Moving {len(files_to_move)} files."
    ):
        move_file(
            source=Path(file_to_move),
            destination=Path(arguments["destination"]) / Path(all_target_dir),
            quiet=arguments["quiet"],
        )

move_file(source, destination, quiet=True)

Move a file.

Parameters:

Name Type Description Default
source Path

Path to the source file.

required
destination Path

Destination directory.

required
quiet bool

Suppress logging output.

True

Returns:

Type Description
Path

Returns the Path the file is moved to.

Source code in mvdate/mvdate.py
def move_file(source: Path, destination: Path, quiet: bool = True) -> Path:
    """
    Move a file.

    Parameters
    ----------
    source : Path
        Path to the source file.
    destination : Path
        Destination directory.
    quiet : bool
        Suppress logging output.

    Returns
    -------
    Path
        Returns the Path the file is moved to.
    """
    source = Path(source)
    destination = Path(destination) / source.name

    try:
        destination = shutil.move(source, destination)
        if not quiet:
            logger.info(f"Moved : {source} -> {destination}")
        return destination
    except FileNotFoundError as fnfe:
        raise fnfe