3
\$\begingroup\$

My Original - Read date from a file and rename two files using the date

github if you want to see behind the scenes.

I like taking pictures of birds. Lots of pictures of birds. My camera counts 0001 to 9999 and then starts over. So, if I have over 10K photos in a single folder and haven't changed the prefix on the camera, there are duplicates.

I shoot in RAW (Nikon). I edit in LightRoom. When I edit the .NEF (RAW), a XMP (Sidecar) with metadata is created and saved alongside the raw file. These have the same filename so when the NEF is opened, the program knows which metadata file to load with it.

I created this to go through the files of a folder and read the metadata, appending the date to the filename ensuring that the raw file and the metadata file remain with matching names.. It evolved from there.

References

I've only imported System. namespace.

UI

I tried my hand at making a UI and having controls handle arguments, which I've not done before.

enter image description here

It's not the best design, I know, but this is how it looks so the code behind it has context.

Code behind UI

Public Class UserInputForm
    Private Sub DirectoryBrowseButton_Click(sender As Object, e As EventArgs) Handles DirectoryBrowseButton.Click
        If (FolderBrowserDialog1.ShowDialog()) Then
            DirectoryDisplayField.Text = FolderBrowserDialog1.SelectedPath & "\"
        End If
    End Sub

    Private Sub RenameCheckBox_CheckedChanged(sender As Object, e As EventArgs) Handles RenameCheckBox.CheckedChanged
        If RenameCheckBox.Checked Then
            RenameTextBox.Visible = True
        Else
            RenameTextBox.Visible = False
        End If
    End Sub

    Private Sub AppendComboBox_SelectedIndexChanged(sender As Object, e As EventArgs) Handles AppendComboBox.SelectedIndexChanged
        If AppendComboBox.Text = "Renumber" Then
            DigitLabel.Visible = True
            DigitInput.Visible = True
        Else
            DigitLabel.Visible = False
            DigitInput.Visible = False
        End If
    End Sub

    Public Sub BeginButton_Click(sender As Object, e As EventArgs) Handles BeginButton.Click
        Dim startingNumber As Integer = -1
        Dim baseName As String = Nothing
        Dim useDate As Boolean = True

        If Not DirectoryDisplayField.Text = Nothing Then
            If AppendComboBox.Text = "Renumber" Then useDate = False
            If RenameCheckBox.Checked Then baseName = RenameTextBox.Text
            If AppendComboBox.Text = "Renumber" Then startingNumber = DigitInput.Value


            UserFormSubmit(DirectoryDisplayField.Text, useDate, RenameTextBox.Text, startingNumber)
        End If
    End Sub

    Private Sub UserInputForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load

    End Sub
End Class

I'm not sure if I should provide the default states of any of the controls?

The Process

Option Explicit On
Option Strict On
Option Infer On
Option Compare Text

Imports System.IO
Module ImageRenaming
    Public Const IMAGE_EXTENTION As String = ".NEF"
    Public Const SIDECAR_EXTENTION As String = ".xmp"

    Public Sub UserFormSubmit(ByVal targetDirectory As String, ByVal UseDate As Boolean, Optional ByVal newName As String = Nothing, Optional ByVal startingNumber As Integer = -1)

        If Not Directory.Exists(targetDirectory) Then Exit Sub
        Dim arrayOfFiles() As String = RetrieveFileNames(targetDirectory)
        If arrayOfFiles.Length = 0 Then Exit Sub
        Dim fileName As String

        Dim numberOfFiles As Integer
        Dim countOfRenames As Integer = startingNumber
        If startingNumber = -1 Then countOfRenames = 1

        'WHAT IF A FILE OF THAT NAME EXISTS? HANDLE THAT
        For index As Integer = 0 To arrayOfFiles.Length - 1
            fileName = arrayOfFiles(index)
            If Path.GetExtension(fileName) = IMAGE_EXTENTION Then
                If arrayOfFiles.Contains(Path.GetFileNameWithoutExtension(fileName) & SIDECAR_EXTENTION) Then
                    numberOfFiles = 2
                Else
                    numberOfFiles = 1
                End If
                countOfRenames = ProcessFile(targetDirectory, Path.GetFileNameWithoutExtension(fileName), numberOfFiles, UseDate, countOfRenames, newName)
            End If
        Next

    End Sub

    Private Function ProcessFile(ByVal targetDirectory As String, ByVal fileName As String, ByVal numberOfFiles As Integer, ByVal useDate As Boolean, ByVal countOfRenames As Integer, Optional ByVal newFileNameBase As String = Nothing) As Integer

        Dim newFileName As String
        If Not newFileNameBase = Nothing And useDate Then
            newFileName = newFileNameBase & countOfRenames
        ElseIf newFileName = Nothing Then
            newFileName = newFileNameBase
        Else
            newFileName = fileName
        End If
        newFileName += "_"
        Dim appendString As String = countOfRenames.ToString

        If numberOfFiles = 2 Then
            If useDate Then appendString = GetFileDate(targetDirectory, Path.ChangeExtension(fileName, SIDECAR_EXTENTION))
            newFileName += appendString

        End If
        RenamePhotoFiles(targetDirectory, fileName, newFileName, numberOfFiles)

        countOfRenames += 1
        Return countOfRenames
    End Function
    Private Function RetrieveFileNames(ByVal targetPath As String) As String()
        Dim fileArray() As String = Directory.GetFiles(targetPath)
        For i As Integer = 0 To fileArray.Length - 1
            fileArray(i) = Path.GetFileName(fileArray(i))
        Next
        Return fileArray
    End Function
    Private Sub RenamePhotoFiles(ByVal targetDirectory As String, ByVal oldName As String, ByVal newName As String, ByVal numberOfFiles As Integer)
        If numberOfFiles = 2 Then
            File.Move(targetDirectory & oldName & SIDECAR_EXTENTION, targetDirectory & newName & SIDECAR_EXTENTION)
        End If
        File.Move(targetDirectory & oldName & IMAGE_EXTENTION, targetDirectory & newName & IMAGE_EXTENTION)
    End Sub

    Private Function GetFileDate(ByVal targetDirectory As String, ByVal sidecarFile As String) As String
        Const LINE_START As String = "   exif:DateTimeOriginal="
        Const DATE_STRING_LENGTH As Integer = 10
        Const CHAR_TO_REMOVE As String = "-"
        sidecarFile = targetDirectory & sidecarFile
        Dim lines = File.ReadAllLines(sidecarFile)
        Dim targetLine As String = Nothing
        For i As Integer = 0 To lines.Length - 1
            If lines(i).StartsWith(LINE_START) Then
                targetLine = lines(i)
                Exit For
            End If
        Next
        If targetLine.Length = 0 Then Return Nothing
        targetLine = targetLine.Substring(LINE_START.Length + 1, DATE_STRING_LENGTH)
        Return targetLine.Replace(CHAR_TO_REMOVE, "")
    End Function


End Module

Main issues

I don't think I have used the most precise naming for some of the variables and functions, so maybe there's a better way to refactor it?

I also don't know if I've anticipated the possible errors. I know I haven't addressed a file already existing.

I also put optional arguments in the userform submit, but I don't know how to make the submit control determine what arguments to send and still execute, so there's duplication on those.

I also would like to make sure I'm doing it the ".NET way" instead of falling back on VBA/VB6 style.

\$\endgroup\$
1
\$\begingroup\$

in your Code Behind, I noticed that you have a situation where you use the CheckBox's Checked() method to determine another objects boolean property

Private Sub RenameCheckBox_CheckedChanged(sender As Object, e As EventArgs) Handles RenameCheckBox.CheckedChanged
    If RenameCheckBox.Checked Then
        RenameTextBox.Visible = True
    Else
        RenameTextBox.Visible = False
    End If
End Sub

really this should be a one-line method and it will still be pretty clear what it is doing.

Private Sub RenameCheckBox_CheckedChanged(sender As Object, e As EventArgs) Handles RenameCheckBox.CheckedChanged
    RenameTextBox.Visible = RenameCheckBox.Checked
End Sub

This piece of Code you could do something similar, but not exactly the same.

Private Sub AppendComboBox_SelectedIndexChanged(sender As Object, e As EventArgs) Handles AppendComboBox.SelectedIndexChanged
    If AppendComboBox.Text = "Renumber" Then
        DigitLabel.Visible = True
        DigitInput.Visible = True
    Else
        DigitLabel.Visible = False
        DigitInput.Visible = False
    End If
End Sub

I would create a boolean to hold the condition that determines your if/then flow and then assign that to the visible property (you should probably use a different variable name)

Private Sub AppendComboBox_SelectedIndexChanged(sender As Object, e As EventArgs) Handles AppendComboBox.SelectedIndexChanged
    Dim performRenumber As Boolean = AppendComboBox.Text = "Renumber"
    DigitLabel.Visible = performRenumber
    DigitInput.Visible = performRenumber
End Sub

during "The Process" you declare and assign the countOfRenames variable, and then in the next line you assign it again under certain conditions of the startingNumber parameter that was given. personally I think that instead of possibly assigning to countOfRenames twice that you should just leave the declaration and assign in the if/then statement with an else statement. My reasoning for this is that your code would then properly tell the story that there is a choice being made here. here is how I would write it.

Dim countOfRenames As Integer
If startingNumber = -1 Then
    countOfRenames = 1
Else
    countOfRenames = startingNumber
End If

This is a little longer in terms of lines, but it shows exactly what you are doing here and only assigns value to the variable once instead of twice in the case that startingNumber is -1

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.