0

My photo import tool (Picasa) does a great job at importing photos and videos from my phone and camera. What I like is that it creates a subfolder under the Pictures directory based on the Photo Taken Date of each photo/video. So you end up with this structure:

C:\Pictures\2017-02-01\DSC_0001.jpg
C:\Pictures\2017-02-01\DSC_0002.jpg
C:\Pictures\2017-02-01\DSC_0003.mp4 <--- problem

The only problem is that it puts videos in this same structure under Pictures.

As such, I'd like to right a batch script to find and move all video files (.mp4, .avi, .mov) from the C:\Pictures directory to the C:\Videos directory, but also with the date subfolder.... i.e.

Move C:\Pictures\2017-02-01\DSC_0003.mp4 to C:\Videos\2017-02-01\DSC_0003.mp4

Note that the date subfolder may or may not exist under C:\Videos.

Also since these are large video files, and there are a lot of them, I'd prefer a process that actually does a move and not a copy then delete, for the sake of speed and disk space utilization as I am almost out of space (after re-organizing these files, I will be archiving off to a NAS).

Also prefer using RoboCopy, xcopy, or xxcopy as I have them and use them today on my machine. If massively easier using PowerShell scripting, I can learn that if it is easy to do.

Final Solution I used Mofi's answer, but enhanced it just a bit to add a function to calculate the directory string length

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem Define folder with the pictures which is never deleted.
set "PicturesFolder=D:\Users\Chad\PicturesTest"

rem get string length of source directory to later use in a substring type function
call :strlen PicturesFolderDirectoryLength PicturesFolder
echo PicturesFolderDirectoryLength = %PicturesFolderDirectoryLength%

rem Change the current directory to directory with the pictures.
cd /D "%PicturesFolder%"

rem Search recursive in this directory for video files with
rem file extension AVI, MOV, MP4 or MPG and move those files.
for /F "delims=" %%I in ('dir /A-D /B /S *.avi *.mov *.mp4 *.mpg 2^>nul') do call :MoveVideo "%%I"

rem Discard all environment variables defined in this batch code
rem and restore initial current directory before exiting batch file.
endlocal
goto :EOF

rem MoveVideo is a subroutine called with name of current
rem video file name with full path by the FOR loop above.

rem It first defines target path for video file depending on source path
rem by removing the backslash at end and concatenating C:\Videos with the
rem source path omitting the first 11 characters which is C:\Pictures.

rem Then the target directory structure is created with redirecting the
rem error message output by command MD to handle STDERR in case of the
rem target directory already exists to device NUL to suppress it.

rem Next the video file is moved from source to target folder with silently
rem overwriting an already existing file with same name in target folder
rem because of using option /Y. Remove this option if a video file should
rem be kept in pictures folder and an error message should be displayed in
rem case of a video file with same name already existing in target folder.

rem Last the source folder is removed if it is completely empty which means
rem it does not contain any file or subfolder. All parent folders up to the
rem pictures folder are also removed if each parent folder is also empty
rem after deletion of an empty folder.

rem The subroutine is exited with goto :EOF and execution of batch file
rem continues in main FOR loop above with next found video file.

:MoveVideo
set "SourcePath=%~dp1"
set "SourcePath=%SourcePath:~0,-1%"
ECHO SourcePath=%SourcePath%

CALL SET "SourceSubFolder=%%SourcePath:~%PicturesFolderDirectoryLength%%%"
ECHO SourceSubFolder=%SourceSubFolder%

set "TargetPath=D:\Users\Chad\VideosTest%SourceSubFolder%"
echo TargetPath=%TargetPath%

md "%TargetPath%" 2>nul
move /Y "%~1" "%TargetPath%\%~nx1" >nul

:DeleteSourceFolder
rd "%SourcePath%" 2>nul
if errorlevel 1 goto :EOF
for /F "delims=" %%D in ("%SourcePath%") do set "SourcePath=%%~dpD"
set "SourcePath=%SourcePath:~0,-1%"
if /I not "%SourcePath%" == "%PicturesFolder%" goto DeleteSourceFolder
goto :EOF

:strlen <resultVar> <stringVar>
(   
    setlocal EnableDelayedExpansion
    set "s=!%~2!#"
    set "len=0"
    for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do (
        if "!s:~%%P,1!" NEQ "" ( 
            set /a "len+=%%P"
            set "s=!s:~%%P!"
        )
    )
)
( 
    endlocal
    set "%~1=%len%"
    exit /b
)
crichavin
  • 4,672
  • 10
  • 50
  • 95

2 Answers2

2

Here is a commented batch code for this file moving task with keeping directory structure.

@echo off
setlocal EnableExtensions DisableDelayedExpansion

rem Define folder with the pictures which is never deleted.
rem Note: ~11 in third line of subroutine MoveVideo must be
rem       replaced by ~length of the folder path defined here.
set "PicturesFolder=C:\Pictures"

rem Change the current directory to directory with the pictures.
cd /D "%PicturesFolder%"

rem Search recursive in this directory for video files with
rem file extension AVI, MOV, MP4 or MPG and move those files.
for /F "delims=" %%I in ('dir /A-D /B /S *.avi *.mov *.mp4 *.mpg 2^>nul') do call :MoveVideo "%%I"

rem Discard all environment variables defined in this batch code
rem and restore initial current directory before exiting batch file.
endlocal
goto :EOF

rem MoveVideo is a subroutine called with name of current
rem video file name with full path by the FOR loop above.

rem It first defines target path for video file depending on source path
rem by removing the backslash at end and concatenating C:\Videos with the
rem source path omitting the first 11 characters which is C:\Pictures.

rem Then the target directory structure is created with redirecting the
rem error message output by command MD to handle STDERR in case of the
rem target directory already exists to device NUL to suppress it.

rem Next the video file is moved from source to target folder with silently
rem overwriting an already existing file with same name in target folder
rem because of using option /Y. Remove this option if a video file should
rem be kept in pictures folder and an error message should be displayed in
rem case of a video file with same name already existing in target folder.

rem Last the source folder is removed if it is completely empty which means
rem it does not contain any file or subfolder. All parent folders up to the
rem pictures folder are also removed if each parent folder is also empty
rem after deletion of an empty folder.

rem The subroutine is exited with goto :EOF and execution of batch file
rem continues in main FOR loop above with next found video file.

:MoveVideo
set "SourcePath=%~dp1"
set "SourcePath=%SourcePath:~0,-1%"
set "TargetPath=C:\Videos%SourcePath:~11%"
md "%TargetPath%" 2>nul
move /Y "%~1" "%TargetPath%\%~nx1" >nul

:DeleteSourceFolder
rd "%SourcePath%" 2>nul
if errorlevel 1 goto :EOF
for /F "delims=" %%D in ("%SourcePath%") do set "SourcePath=%%~dpD"
set "SourcePath=%SourcePath:~0,-1%"
if /I not "%SourcePath%" == "%PicturesFolder%" goto DeleteSourceFolder
goto :EOF

This batch file also removes all folders in C:\Pictures which become empty after moving the video files. But it does not remove folders which were already empty on starting the batch file.

For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.

  • cd /?
  • echo /?
  • endlocal /?
  • for /?
  • goto /?
  • if /?
  • md /?
  • move /?
  • rd /?
  • rem /?
  • set /?
  • setlocal /?

Read also the Microsoft article about Using Command Redirection Operators for an explanation of >nul and 2>nul. In the main FOR loop the redirection operator > is escaped with caret character ^ to be interpreted as literal character on parsing FOR command line and later as redirection operator on execution of DIR command line by FOR.

Mofi
  • 46,139
  • 17
  • 80
  • 143
  • worked like a charm and nice feature to delete empty source directories!!! Plus excellent code documentation! I enhanced your solution just slightly to have a function to calculate the source folder directory length so as not to have it hardcoded in case I want to use this as a baseline for other tasks later. See my final answer updated in the original question. – crichavin Feb 22 '17 at 04:38
0
@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=U:\sourcedir"
SET "destdir=U:\destdir"
XCOPY /T "%sourcedir%" "%destdir%"
FOR %%x IN (mp4 mov) DO (
 FOR /f "tokens=1*delims=>" %%a IN (
  'XCOPY /Y /s /d /F /L "%sourcedir%\*.%%x" "%destdir%"'
 ) DO IF "%%b" neq "" (
  SET "topart=%%b"
  SET "frompart=%%a"
  ECHO(MOVE /y "!frompart:~0,-2!" "!topart:~1!"
 )
)    


GOTO :EOF

You would need to change the settings of sourcedir and destdir to suit your circumstances.

The required MOVE commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(MOVE to MOVE to actually move the files. Append >nul to suppress report messages (eg. 1 file moved)

The first xcopy creates the required subtrees, the second uses the /L option to list rather than copy the files

The loop on %%x assigns %%x to the required extensions. The output from the inner xcopy will be of the form fullsourcefilename -> fulldestinationfilename so it needs to be parsed using > as a delimiter, from-filename to %%a, to-filename to %%b. If %%b is not set, then this is the final line of xcopy's report (n files copied) which needs to be ignored. The to and from filenames need to be trimmed of unwanted, but fortunately constant character strings.

What is interesting is that there appears to be no way using xcopy to suppress prompting in the case where the destination filename already exists.

Magoo
  • 77,302
  • 8
  • 62
  • 84
  • On both lines with __XCOPY__ command the target path should be `"%destdir%\"` with a backslash at end to make it clear for __XCOPY__ that the target is a directory and not a file to avoid being asked if target is a file or a directory. In this case also using __XCOPY__ option `/I` would avoid being asked if target is a file or a directory. But `/I` works only on copying multiple files while backslash at end of target works also on copying a single file. – Mofi Feb 21 '17 at 08:14
  • The prompt can't be avoided on using __XCOPY__ for copying a single (hidden) file to a target folder (not yet existing) with a different name in target folder. I posted at [BATCH file asks for file or folder](http://stackoverflow.com/a/35829012/3074564) a solution for this special problem as the prompt is language dependent and needs extra code if the batch file should answer the prompt automatically independent on language. – Mofi Feb 21 '17 at 08:16
  • @mofi : The first xcopy, granted. The second one - no, since it will already exist as a directory. The target directory will exist since the first `xcopy` creates it. Seems that (my interpretation of) the `y` switch for `xcopy` doesn't work. I suppose I should mention to to M$, but the chances of having it fixed - well, I'd be a billionaire by playing the lottery first... – Magoo Feb 21 '17 at 08:44
  • The first __XCOPY__ command line just creating the directory tree in destination directory also needs `/Y` to avoid prompting to override the already existing *.mp4 file although the *.mp4 file is not copied at all. It is indeed interesting that although `/T` is used and so no file is copied, __XCOPY__ asks on overwriting existing files in destination directory. – Mofi Feb 21 '17 at 09:18