Skip to content

Commit

Permalink
Reorganizes content into more sensible files
Browse files Browse the repository at this point in the history
The original `sync_and_...` file was unnecessary.
  • Loading branch information
topfunky committed May 15, 2024
1 parent 5a8d827 commit 0d7cc32
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 252 deletions.
62 changes: 0 additions & 62 deletions find_and_transcode_files.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,28 +51,6 @@ func findAndTranscodeFiles(sourceDir, destinationDir string) error {
return nil
}

// isUntranscodedMusicFile checks if the path is a source music file of common types that need to be converted to MP3 (but are not themselves MP3), based on its extension.
func isUntranscodedMusicFile(path string) bool {
extensions := []string{".aif", ".wav", ".m4a"}
return stringInSlice(filepath.Ext(path), extensions)
}

// stringInSlice returns bool if a string is found in any of a list of other strings.
//
// Example usage:
//
// if stringInSlice("Stevia", []string{"Stevie Nicks", "Stevie Wonder", "Steve Nash", "Steve McQueen"}) {
//
// }
func stringInSlice(str string, list []string) bool {
for _, v := range list {
if v == str {
return true
}
}
return false
}

// copyFile copies a file from the source path to the destination path.
// It creates any necessary directories in the destination path.
// If the file cannot be copied for any reason, it returns an error.
Expand Down Expand Up @@ -228,43 +206,3 @@ func convertSourceToDestinationFilename(filename string) string {

return filename
}

// removeNonASCII replaces non-ASCII characters in a string with an ASCII equivalent.
func removeNonASCII(str string) string {
// Create a hashmap to store non-ASCII characters as keys and ASCII characters as values
nonASCIItoASCII := map[rune]rune{
'á': 'a',
'é': 'e',
'è': 'e',
'ê': 'e',
'í': 'i',
'ó': 'o',
'ø': 'o',
'ú': 'u',
'ñ': 'n',
'Á': 'A',
'É': 'E',
'È': 'E',
'Ê': 'E',
'Í': 'I',
'Ó': 'O',
'Ø': 'O',
'Ú': 'U',
'Ñ': 'N',
// Add more mappings as needed
}

// Replace non-ASCII characters with their ASCII equivalents
var result strings.Builder
for _, char := range str {
if asciiChar, ok := nonASCIItoASCII[char]; ok {
result.WriteRune(asciiChar)
} else if char > maxASCIIIndex {
// Don't emit char
} else {
result.WriteRune(char)
}
}

return result.String()
}
192 changes: 182 additions & 10 deletions find_and_transcode_files_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
package main

import (
"fmt"
"os"
"os/exec"
"path/filepath"
"testing"

"log"

"time"

"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -96,16 +104,6 @@ func TestGetExclusiveFiles(t *testing.T) {
}
}

// Returns a string array of only the `sourcePath` attribute from an array of `fileToRender` structs.
//
// This makes test assertions cleaner, based on how the fixture data is written.
func getDestinationPaths(files []fileToRender) []string {
var sources []string
for _, file := range files {
sources = append(sources, file.destinationPath)
}
return sources
}
func TestConvertSourceToDestinationFilename(t *testing.T) {
cases := []struct {
Name string
Expand Down Expand Up @@ -142,3 +140,177 @@ func TestConvertSourceToDestinationFilename(t *testing.T) {
})
}
}

func generateM4aFixtureFileAtPath(path string) error {
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
return fmt.Errorf("failed to create directories: %v", err)
}
cmd := exec.Command("ffmpeg", "-f", "lavfi", "-i", "sine=frequency=1000:duration=5", path)
err := cmd.Run()
if err != nil {
log.Fatal(err)
return err
}
return nil
}

func generateTextFileFixtureAtPath(path string) error {
if err := os.WriteFile(path, []byte{}, 0644); err != nil {
log.Fatal(err)
return err
}
return nil
}

func setupFixtureFilesInDirectory(tempDir string, numberOfFiles int) error {
// Create a directory within tempDir named "source"
sourceDir := filepath.Join(tempDir, "source")
if err := os.Mkdir(sourceDir, 0755); err != nil {
return fmt.Errorf("failed to create source directory: %v", err)
}

// Create some test files with .m4a extension
testFiles := []string{
"source/file1.m4a",
"source/file2.m4a",
"source/Alexandra Stréliski/Néo-Romance (Extended Version) [96kHz · 24bit]/02 - Lumières.m4a",
"source/a-band/file5.m4a",
"source/Whitespace Band/file6.m4a",
"source/the-band/file7.mp3",
"source/file8.aif",
"source/file9.wav",
"source/.DS_Store",
}
for _, file := range testFiles[0:numberOfFiles] {
filePath := filepath.Join(tempDir, file)
if err := generateM4aFixtureFileAtPath(filePath); err != nil {
return fmt.Errorf("Failed to create test file: %v", err)
}
}

// A text file that is not an m4a file
testTextFileName := "file3.txt" // Not an .m4a file
textFilePath := filepath.Join(tempDir, testTextFileName)
if err := generateTextFileFixtureAtPath(textFilePath); err != nil {
return fmt.Errorf("failed to create test text file: %v", err)
}

return nil
}

func setup(t *testing.T, numberOfFiles int) (string, error) {
// Create a temporary directory for testing
tempDir, err := os.MkdirTemp("", "test")
if err != nil {
t.Fatalf("failed to create temporary directory: %v", err)
}

// Set up fixture files in the temporary directory
if err := setupFixtureFilesInDirectory(tempDir, numberOfFiles); err != nil {
t.Fatalf("failed to set up fixture files: %v", err)
}
return tempDir, nil
}

func TestFindFiles(t *testing.T) {
transcodedFiles := []string{
"destination/file1.mp3",
"destination/file2.mp3",
"destination/Alexandra Streliski/Neo-Romance (Extended Version) [96kHz 24bit]/02 - Lumieres.mp3",
"destination/a-band/file5.mp3",
"destination/Whitespace Band/file6.mp3",
"destination/the-band/file7.mp3",
"destination/file8.mp3",
"destination/file9.mp3",
// NOTE: Do not list .DS_Store or .txt files since they should not be transcoded
}

tempDir, err := setup(t, len(transcodedFiles))
if err != nil {
t.Fatalf("failed to set up fixture files: %v", err)
}

defer os.RemoveAll(tempDir)

findAndTranscodeFiles(filepath.Join(tempDir, "source"), filepath.Join(tempDir, "destination"))

for _, file := range transcodedFiles {
t.Run(fmt.Sprintf("File %s should be rendered", file), func(t *testing.T) {
filePath := filepath.Join(tempDir, file)
assert.FileExistsf(t, filePath, "Transcoded file not found: %s", file)
})
}

t.Run("Verify that the non-.m4a file was not transcoded", func(t *testing.T) {
nonTranscodedFile := "file3.txt.transcoded"
filePath := filepath.Join(tempDir, nonTranscodedFile)
assert.NoFileExistsf(t, filePath, "unexpected transcoded file found: %s", nonTranscodedFile)
})

t.Run("Verify that the .DS_Store file was not transcoded", func(t *testing.T) {
nonTranscodedFile := ".DS_Store"
filePath := filepath.Join(tempDir, nonTranscodedFile)
assert.NoFileExistsf(t, filePath, "unexpected transcoded file found: %s", nonTranscodedFile)
})
}

func TestFindFiles_EmptyDestinationDirectory(t *testing.T) {
transcodedFiles := []string{}

tempDir, err := setup(t, len(transcodedFiles))
defer os.RemoveAll(tempDir)

if err != nil {
t.Fatalf("❗️ Failed to create temporary directory: %v", err)
}

sourceDir := filepath.Join(tempDir, "source")
destinationDir := filepath.Join(tempDir, "destination dir that does not exist")

err = findAndTranscodeFiles(sourceDir, destinationDir)
assert.NoError(t, err)

}

// Destination files should not be re-rendered (check file modified time from first render and compare to second render)
func TestFindFiles_NoReRender(t *testing.T) {
// Generate limited test fixtures with one media file.
tempDir, _ := setup(t, 1)
defer os.RemoveAll(tempDir)

sourceDir := filepath.Join(tempDir, "source")
destinationDir := filepath.Join(tempDir, "destination")

// Run the function for the first time
findAndTranscodeFiles(sourceDir, destinationDir)

// Verify that the destination files were not re-rendered
file := "source/file1.m4a"
t.Run(fmt.Sprintf("File %s should not be re-rendered", file), func(t *testing.T) {
destinationPath := filepath.Join(tempDir, "destination/file1.mp3")

info1, _ := os.Stat(destinationPath)
assert.FileExistsf(t, destinationPath, "Transcoded file not found: %s", file)

// Wait for a second to ensure the modified time is different
time.Sleep(time.Second)

findAndTranscodeFiles(sourceDir, destinationDir)

info2, _ := os.Stat(destinationPath)
assert.FileExistsf(t, destinationPath, "Transcoded file not found: %s", file)

assert.Equal(t, info1.ModTime(), info2.ModTime(), fmt.Sprintf("file %s was re-rendered", destinationPath))
})
}

// Returns a string array of only the `sourcePath` attribute from an array of `fileToRender` structs.
//
// This makes test assertions cleaner, based on how the fixture data is written.
func getDestinationPaths(files []fileToRender) []string {
var sources []string
for _, file := range files {
sources = append(sources, file.destinationPath)
}
return sources
}
67 changes: 67 additions & 0 deletions helper.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package main

import (
"path/filepath"
"strings"
)

const maxASCIIIndex = 127

// containsNonASCII returns true if a string contains non-ASCII characters.
Expand All @@ -11,3 +16,65 @@ func containsNonASCII(str string) bool {
}
return false
}

// removeNonASCII replaces non-ASCII characters in a string with an ASCII equivalent.
func removeNonASCII(str string) string {
// Create a hashmap to store non-ASCII characters as keys and ASCII characters as values
nonASCIItoASCII := map[rune]rune{
'á': 'a',
'é': 'e',
'è': 'e',
'ê': 'e',
'í': 'i',
'ó': 'o',
'ø': 'o',
'ú': 'u',
'ñ': 'n',
'Á': 'A',
'É': 'E',
'È': 'E',
'Ê': 'E',
'Í': 'I',
'Ó': 'O',
'Ø': 'O',
'Ú': 'U',
'Ñ': 'N',
// Add more mappings as needed
}

// Replace non-ASCII characters with their ASCII equivalents
var result strings.Builder
for _, char := range str {
if asciiChar, ok := nonASCIItoASCII[char]; ok {
result.WriteRune(asciiChar)
} else if char > maxASCIIIndex {
// Don't emit char
} else {
result.WriteRune(char)
}
}

return result.String()
}

// isUntranscodedMusicFile checks if the path is a source music file of common types that need to be converted to MP3 (but are not themselves MP3), based on its extension.
func isUntranscodedMusicFile(path string) bool {
extensions := []string{".aif", ".wav", ".m4a"}
return stringInSlice(filepath.Ext(path), extensions)
}

// stringInSlice returns bool if a string is found in any of a list of other strings.
//
// Example usage:
//
// if stringInSlice("Stevia", []string{"Stevie Nicks", "Stevie Wonder", "Steve Nash", "Steve McQueen"}) {
//
// }
func stringInSlice(str string, list []string) bool {
for _, v := range list {
if v == str {
return true
}
}
return false
}
3 changes: 1 addition & 2 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"flag"
"io/ioutil"
"os"
"path/filepath"
"testing"
Expand All @@ -12,7 +11,7 @@ import (

func setupMainTest(t *testing.T) (string, error) {
// Create a temporary directory for testing
tempDir, err := ioutil.TempDir("", "test")
tempDir, err := os.MkdirTemp("", "test")
if err != nil {
t.Fatalf("Failed to create temporary directory: %v", err)
}
Expand Down
Loading

0 comments on commit 0d7cc32

Please sign in to comment.