diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/header_template.txt | 14 | ||||
| -rw-r--r-- | src/main.odin | 109 | ||||
| -rw-r--r-- | src/wav/wav.odin | 250 |
3 files changed, 223 insertions, 150 deletions
diff --git a/src/header_template.txt b/src/header_template.txt new file mode 100644 index 0000000..8d42a55 --- /dev/null +++ b/src/header_template.txt @@ -0,0 +1,14 @@ +# Header info fields +# +# In this file, you can write things you want to appear in the header of the sound report. +# These fields will be inserted into sound reports generated directly from wav metadata. +# This happens when you submit a folder with .WAV-files that doesn't contain a CSV file. +# +# Empty lines, and lines starting with # are ignored. +# No need to add facts about the files, those are added automatically from metadata. + +Sound Mixer: Ola Nordmann +Phone: +0 123 45 678 +Email: ola@nordmann.no +Boom Operator: Sven Svensson +Project: Project Name diff --git a/src/main.odin b/src/main.odin index 71ae6ed..37c17c1 100644 --- a/src/main.odin +++ b/src/main.odin @@ -6,6 +6,7 @@ import "core:os/os2" import "core:path/filepath" import "core:sys/windows" import "core:strings" +import "wav" /* TODO: Simplify pre-allocation. Just allocate a bunch. It's probably fine. @@ -20,6 +21,10 @@ PART_ONE :: #load("parts/start.html", string) PART_TWO :: #load("parts/start2.html", string) PART_END :: #load("parts/end.html", string) +HEADER_TEMPLATE :: #load("header_template.txt", string) +HEADER_FIELDS_PATH :: "info.txt" +header_fields_file : string + Device :: enum { UNSET, ZOOM, @@ -50,6 +55,12 @@ Report :: struct { tc_column_index : int, } +CSV :: string +Directory :: [dynamic]string +Job :: union {CSV, Directory} +job_list :: [dynamic]Job +// TODO: Changing file_list to job_list, so the Directory jobs can contain a list of all the relevant .wav files before being sent to parse_folder() + main :: proc() { when ODIN_OS == .Windows { windows.SetConsoleOutputCP(windows.CODEPAGE.UTF8) @@ -57,13 +68,20 @@ main :: proc() { input_file_name : string if len(os.args) < 2 { - fmt.println("ERROR: No file submitted.") + fmt.println("No paths submitted.") + if os.is_file(HEADER_FIELDS_PATH) { + fmt.printfln("\"%v\" already exists.", HEADER_FIELDS_PATH) + } else { + os.write_entire_file(HEADER_FIELDS_PATH, transmute([]u8)HEADER_TEMPLATE) + fmt.printfln("Created \"%v\".", HEADER_FIELDS_PATH) + } return - } else { - fmt.printf("Input path: {}\n", os.args[1]) - input_file_name = os.args[1] } + fmt.printf("Input path: {}\n", os.args[1]) + input_file_name = os.args[1] + + path_info, error := os.stat(input_file_name) @@ -71,22 +89,21 @@ main :: proc() { files_done := 0 if error == os.ERROR_NONE { - file_list : [dynamic]string if(path_info.is_dir) { fmt.printf("Directory submitted! Walking directory...\n\n") fmt.printf("š {}\n", path_info.name) - try_os2 := walk_directory(path_info.fullpath, &file_count, &file_list, 1) - if len(file_list) == 0 && try_os2 { + try_os2 := walk_directory(path_info.fullpath, &file_count, &job_list, 1) + if len(job_list) == 0 && try_os2 { fmt.printf("\nNot_Dir error encountered. Trying os2 version...\n\n") fmt.printf("š {}\n", path_info.name) - walk_directory_os2(path_info.fullpath, &file_count, &file_list, 1) + walk_directory_os2(path_info.fullpath, &file_count, &job_list, 1) } } else { fmt.println("File submitted! Processing file...") - append(&file_list, strings.clone(path_info.fullpath)) + append(&job_list, strings.clone(path_info.fullpath)) } - for file, f in file_list { + for file, f in job_list { file_info, _ := os.stat(file) fmt.printf("\nš File {}: {}\n", f+1, file_info.name) @@ -99,8 +116,9 @@ main :: proc() { render(parsed, output_name) free_all(context.temp_allocator) files_done += 1 + } - fmt.printf("\nCompleted {}/{} files.\n\n", files_done, len(file_list)) + fmt.printf("\nCompleted {}/{} files.\n\n", files_done, len(job_list)) } else { fmt.printf("ERROR could not get path info for: {}\n", input_file_name) } @@ -108,9 +126,19 @@ main :: proc() { } +parse :: proc(path : string, device : Device = .UNSET) -> (Report, bool) { + if os.is_dir(path) { + return parse_folder(path) + } + return parse_file(path, device) +} +parse_folder :: proc(path : string) -> (Report, bool) { + output : Report = {} + return output, false +} -parse :: proc(path : string, device : Device = .UNSET) -> (Report, bool) { +parse_file :: proc(path : string, device : Device = .UNSET) -> (Report, bool) { device := device output : Report = {} data, ok := os.read_entire_file(path, context.temp_allocator) @@ -346,8 +374,6 @@ parse :: proc(path : string, device : Device = .UNSET) -> (Report, bool) { } } - fmt.printf("\n") - @@ -666,16 +692,13 @@ render :: proc(report : Report, path : string) { fmt.printf("Output: {}\n", path) } - - - indent_by :: proc(i : int) { for x in 0..<i { fmt.printf(" ") } } -walk_directory :: proc(path : string, file_number : ^int, file_list : ^[dynamic]string, depth : int = 0) -> bool { +walk_directory :: proc(path : string, file_number : ^int, job_list : ^[dynamic]string, depth : int = 0) -> bool { handle, ok := os.open(path) if ok != os.ERROR_NONE { indent_by(depth) @@ -692,6 +715,8 @@ walk_directory :: proc(path : string, file_number : ^int, file_list : ^[dynamic] return true } + wav_count := 0 + has_csv := false for file in files { @@ -700,24 +725,38 @@ walk_directory :: proc(path : string, file_number : ^int, file_list : ^[dynamic] if file.is_dir { indent_by(depth) fmt.printf("š %s\n", file.name) - walk_directory(full_path, file_number, file_list, depth+1) // Recurse + walk_directory(full_path, file_number, job_list, depth+1) // Recurse } else { // If file is actually a file extension := strings.to_lower(filepath.ext(file.name)) defer delete(extension) - if(extension == ".csv"){ + if extension == ".csv" { indent_by(depth) - fmt.printf("š %d %s\n", file_number^, file.name) - append(file_list, strings.clone(file.fullpath)) + fmt.printf("š [#%d] %s\n", file_number^, file.name) + append(job_list, strings.clone(file.fullpath)) file_number^ += 1 + has_csv = true + } + if extension == ".wav" { + wav_count += 1 } } } + if wav_count>0 && !has_csv { + indent_by(depth+1) + if wav_count == 1 { + fmt.printf("š½ [#%d] 1 WAV file, without CSV.\n", file_number^) + } else { + fmt.printf("š½ [#%d] %d WAV files, without CSV.\n", file_number^, wav_count) + } + append(job_list, strings.clone(path)) + file_number^ += 1 + } return false } -walk_directory_os2 :: proc(path : string, file_number : ^int, file_list : ^[dynamic]string, depth : int = 0) { +walk_directory_os2 :: proc(path : string, file_number : ^int, job_list : ^[dynamic]string, depth : int = 0) { handle, ok := os2.open(path) if ok != os2.ERROR_NONE { indent_by(depth) @@ -733,6 +772,8 @@ walk_directory_os2 :: proc(path : string, file_number : ^int, file_list : ^[dyna return } + wav_count := 0 + has_csv := false for file in files { @@ -741,18 +782,32 @@ walk_directory_os2 :: proc(path : string, file_number : ^int, file_list : ^[dyna if os.is_dir(full_path) { indent_by(depth) fmt.printf("š %s\n", file.name) - walk_directory_os2(full_path, file_number, file_list, depth+1) // Recurse + walk_directory_os2(full_path, file_number, job_list, depth+1) // Recurse } else { // If file is actually a file extension := strings.to_lower(filepath.ext(file.name)) defer delete(extension) - if(extension == ".csv"){ + if extension == ".csv" { indent_by(depth) - fmt.printf("š %d %s\n", file_number^, file.name) - append(file_list, strings.clone(file.fullpath)) + fmt.printf("š [#%d] %s\n", file_number^, file.name) + append(job_list, strings.clone(file.fullpath)) file_number^ += 1 + has_csv = true + } + if extension == ".wav" { + wav_count += 1 } } } + if wav_count>0 && !has_csv { + indent_by(depth+1) + if wav_count == 1 { + fmt.printf("š½ [#%d] A WAV file, without CSV.\n", file_number^) + } else { + fmt.printf("š½ [#%d] %d WAV files, without CSV.\n", file_number^, wav_count) + } + append(job_list, strings.clone(path)) + file_number^ += 1 + } }
\ No newline at end of file diff --git a/src/wav/wav.odin b/src/wav/wav.odin index f2cf85c..01fbad1 100644 --- a/src/wav/wav.odin +++ b/src/wav/wav.odin @@ -18,10 +18,7 @@ Wav :: struct { reported_size : u32, // Metadata - buf : []u8, channel_names : []string, - bext : []u8, - ixml : string, samples_since_midnight: u64, timecode : Timecode, tc_framerate : f32, @@ -56,38 +53,44 @@ main :: proc() { fmt.printf("\n\nf8 = %#v\n\n", f8) } -// TODO: Maybe split reading metadata into its own function call. - +/* +Reads in the wav file data, including metadata. +*/ read_wav :: proc(path : string, allocator:=context.allocator) -> (Wav, bool) { file : Wav - file_ok : os.Error - file.handle, file_ok = os.open(path) + load_err : os.Error + file.handle, load_err = os.open(path) defer os.close(file.handle) defer file.handle = 0 - assert(file_ok == os.General_Error.None) + if load_err != os.General_Error.None { + fmt.eprintln("ERROR %v: Unable to load file \"%v\"", load_err, path) + return {}, false + } - file.buf = new([BUFFER_SIZE]u8)[:] - defer delete(file.buf) - os.read(file.handle, file.buf) + temp_buf := new([BUFFER_SIZE]u8)[:] + temp_bext : []u8 + temp_ixml : string + defer delete(temp_buf) + os.read(file.handle, temp_buf) head : int = 0 // RIFF header - fmt.println(string(file.buf[0:4])) - if string(file.buf[0:4]) != "RIFF" do return {}, false + fmt.println(string(temp_buf[0:4])) + if string(temp_buf[0:4]) != "RIFF" do return {}, false head += 4 // Size - file.reported_size = read_little_endian_u32(file.buf[head:head+4]) + file.reported_size = read_little_endian_u32(temp_buf[head:head+4]) fmt.println("Reported size:", file.reported_size) head += 4 - - // Confirming again that this is a wave file - fmt.println(string(file.buf[head:head+4])) - if string(file.buf[head:head+4]) != "WAVE" do return {}, false + + // Confirming again that this is a wave file + fmt.println(string(temp_buf[head:head+4])) + if string(temp_buf[head:head+4]) != "WAVE" do return {}, false head += 4 fmt.println("\nChunks:\n") @@ -95,10 +98,10 @@ read_wav :: proc(path : string, allocator:=context.allocator) -> (Wav, bool) { // Looping through chunks null_chunks := 0 for _ in 0..<BUFFER_SIZE { - chunk_id_raw := file.buf[head:head+4] + chunk_id_raw := temp_buf[head:head+4] chunk_id := string(chunk_id_raw) head += 4 - chunk_size := int(read_little_endian_u32(file.buf[head:head+4])) + chunk_size := int(read_little_endian_u32(temp_buf[head:head+4])) head += 4 fmt.println(chunk_id, chunk_size,"\n-------------------------------------") data_reached := false @@ -110,21 +113,21 @@ read_wav :: proc(path : string, allocator:=context.allocator) -> (Wav, bool) { read_end := min(head+15000, data_end) switch chunk_id { case "iXML": - //print_data = string(file.buf[head:read_end]) - file.ixml = strings.clone(string(file.buf[head:data_end]), allocator=allocator) + temp_ixml = string(temp_buf[head:data_end]) + //print_data = temp_ixml null_chunks = 0 case "bext": - print_data = string(file.buf[head:read_end]) - file.bext = file.buf[head:data_end] + temp_bext = temp_buf[head:data_end] + //print_data = string(temp_bext) null_chunks = 0 case "fmt ": - file.format = Audio_Format(read_little_endian_u16(file.buf[head:])) + file.format = Audio_Format(read_little_endian_u16(temp_buf[head:])) fmt.println("Format:", file.format) head += 2 - file.channels = int(read_little_endian_u16(file.buf[head:])) + file.channels = int(read_little_endian_u16(temp_buf[head:])) fmt.println("Channels:", file.channels) head += 2 - file.sample_rate = int(read_little_endian_u32(file.buf[head:])) + file.sample_rate = int(read_little_endian_u32(temp_buf[head:])) fmt.println("Sample rate:", file.sample_rate) head += 4 @@ -133,7 +136,7 @@ read_wav :: proc(path : string, allocator:=context.allocator) -> (Wav, bool) { // and are implied by the other fields. head += 4 + 2 - file.bit_depth = int(read_little_endian_u16(file.buf[head:])) + file.bit_depth = int(read_little_endian_u16(temp_buf[head:])) fmt.println("Bit depth:", file.bit_depth) head += 2 head = data_end @@ -167,104 +170,15 @@ read_wav :: proc(path : string, allocator:=context.allocator) -> (Wav, bool) { - // bext parsing - if len(file.bext) > 0 { - - // Stripping null padding - end := len(file.bext) - 1 - for end >= 0 && file.bext[end] == 0 { - end -= 1 - } - file.bext = file.bext[:end] - - naming_channel := 0 - - description := string(file.bext[:256]) - for line in strings.split_lines(description) { - if strings.starts_with(line, "sTRK") || strings.starts_with(line, "zTRK") { - eq_index := strings.index(line, "=") - file.channel_names[naming_channel] = strings.clone(line[eq_index+1:], allocator=allocator) - naming_channel += 1 - } - if strings.starts_with(line[1:], "TAKE=") { - file.take, _ = strconv.parse_int(line[6:]) - } - if strings.starts_with(line[1:], "CIRCLED=") { - file.circled = line[9:] == "TRUE" - } - if strings.starts_with(line[1:], "SPEED=") { - value := line[7:] - num_end : int - type_start : int - for r, i in value { - if !strings.contains_rune("1234567890.", r) && num_end == 0 { - num_end = i - } - if r=='N' || r=='D' { - type_start = i - break - } - } - file.tc_framerate, _ = strconv.parse_f32(value[:num_end]) - file.tc_dropframe = value[type_start:] != "ND" - - } - // Only if ixml doesn't exist, so we don't allocate the note string twice. - if file.ixml == "" { - if strings.starts_with(line[1:], "NOTE=") { - v := line[6:] - if v != "" { - file.note = strings.clone(v, allocator=allocator) - } - } - } - } - head := 0 - fmt.printf("Description: \n%v\n", string(file.bext[head:256])) - head += 256 - fmt.printf("Originator: %v\n", string(file.bext[head:head+32])) - head += 32 - fmt.printf("Originator Reference: %v\n", string(file.bext[head:head+32])) - head += 32 - fmt.printf("Origination Date: %v\n", string(file.bext[head:head+10])) - head += 10 - fmt.printf("Origination Time: %v\n", string(file.bext[head:head+8])) - head += 8 - - file.samples_since_midnight = read_little_endian_u64(file.bext[head:head+8]) - - seconds_since_midnight := file.samples_since_midnight / u64(file.sample_rate) - file.timecode.hour = u8( seconds_since_midnight / 3600) - file.timecode.minute = u8((seconds_since_midnight % 3600) / 60) - file.timecode.second = u8( seconds_since_midnight % 60) - file.timecode.frame = f32( f64(file.samples_since_midnight % u64(file.sample_rate) ) * f64(file.tc_framerate) / f64(file.sample_rate)) - fmt.printf("Time Reference: %v (Samples since midnight, source of timecode)\n", file.samples_since_midnight) - fmt.printf(" %v seconds + %v samples\n", seconds_since_midnight, file.samples_since_midnight % u64(file.sample_rate)) - head += 8 - - fmt.printf("Version: %v\n", read_little_endian_u16(file.bext[head:head+2])) - head += 2 - fmt.printf("UMID Skipped.\n") - head += 64 - fmt.printf("Skipped reserved nothingness.\n") - head += 190 - fmt.printf("Coding history:\n%v\n", string(file.bext[head:])) - } - fmt.println() - - // just here to make some printing prettier - file.bext = nil - - // iXML Parsing - if file.ixml != "" { + if temp_ixml != "" { // Stripping null padding - end := len(file.ixml) - 1 - for end >= 0 && file.ixml[end] == 0 { + end := len(temp_ixml) - 1 + for end >= 0 && temp_ixml[end] == 0 { end -= 1 } - file.ixml = file.ixml[:end] + temp_ixml = temp_ixml[:end] naming_channel := 0 @@ -406,14 +320,104 @@ read_wav :: proc(path : string, allocator:=context.allocator) -> (Wav, bool) { parsed_ixml : ^xml.Document - parsed_ixml, _ = xml.parse(file.ixml, xml.Options{ + parsed_ixml, _ = xml.parse(temp_ixml, xml.Options{ flags={.Ignore_Unsupported}, expected_doctype = "", }) xml_recurse(parsed_ixml, 0, &file, &naming_channel, &interleave_set, allocator) } - file.buf = nil + // bext parsing + if len(temp_bext) > 0 { + + // Stripping null padding + end := len(temp_bext) - 1 + for end >= 0 && temp_bext[end] == 0 { + end -= 1 + } + temp_bext = temp_bext[:end] + + naming_channel := 0 + + description := string(temp_bext[:256]) + for line in strings.split_lines(description) { + + if file.channel_names[naming_channel] == "" && + (strings.starts_with(line, "sTRK") || strings.starts_with(line, "zTRK")) { + eq_index := strings.index(line, "=") + file.channel_names[naming_channel] = strings.clone(line[eq_index+1:], allocator=allocator) + naming_channel += 1 + } + if strings.starts_with(line[1:], "TAKE=") { + file.take, _ = strconv.parse_int(line[6:]) + } + if strings.starts_with(line[1:], "CIRCLED=") { + file.circled = line[9:] == "TRUE" + } + if strings.starts_with(line[1:], "SPEED=") { + value := line[7:] + num_end : int + type_start : int + for r, i in value { + if !strings.contains_rune("1234567890.", r) && num_end == 0 { + num_end = i + } + if r=='N' || r=='D' { + type_start = i + break + } + } + file.tc_framerate, _ = strconv.parse_f32(value[:num_end]) + file.tc_dropframe = value[type_start:] != "ND" + + } + // Only if ixml doesn't exist, so we don't allocate the note string twice. + if file.note == "" { + if strings.starts_with(line[1:], "NOTE=") { + v := line[6:] + if v != "" { + file.note = strings.clone(v, allocator=allocator) + } + } + } + } + head := 0 + fmt.printf("Description: \n%v\n", string(temp_bext[head:256])) + head += 256 + fmt.printf("Originator: %v\n", string(temp_bext[head:head+32])) + head += 32 + fmt.printf("Originator Reference: %v\n", string(temp_bext[head:head+32])) + head += 32 + fmt.printf("Origination Date: %v\n", string(temp_bext[head:head+10])) + head += 10 + fmt.printf("Origination Time: %v\n", string(temp_bext[head:head+8])) + head += 8 + + file.samples_since_midnight = read_little_endian_u64(temp_bext[head:head+8]) + + seconds_since_midnight := file.samples_since_midnight / u64(file.sample_rate) + file.timecode.hour = u8( seconds_since_midnight / 3600) + file.timecode.minute = u8((seconds_since_midnight % 3600) / 60) + file.timecode.second = u8( seconds_since_midnight % 60) + file.timecode.frame = f32( f64(file.samples_since_midnight % u64(file.sample_rate) ) * f64(file.tc_framerate) / f64(file.sample_rate)) + fmt.printf("Time Reference: %v (Samples since midnight, source of timecode)\n", file.samples_since_midnight) + fmt.printf(" %v seconds + %v samples\n", seconds_since_midnight, file.samples_since_midnight % u64(file.sample_rate)) + head += 8 + + fmt.printf("Version: %v\n", read_little_endian_u16(temp_bext[head:head+2])) + head += 2 + fmt.printf("UMID Skipped.\n") + head += 64 + fmt.printf("Skipped reserved nothingness.\n") + head += 190 + fmt.printf("Coding history:\n%v\n", string(temp_bext[head:])) + } + fmt.println() + + // just here to make some printing prettier + temp_bext = nil + + temp_buf = nil return file, true } |