From 07f9ad8350df6cd87ed68f9d6d943c769c2c4007 Mon Sep 17 00:00:00 2001 From: San Jacobs Date: Tue, 14 May 2024 05:11:43 +0200 Subject: Wrote the whole thing in a day --- src/main.odin | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 src/main.odin (limited to 'src') diff --git a/src/main.odin b/src/main.odin new file mode 100644 index 0000000..c72b97e --- /dev/null +++ b/src/main.odin @@ -0,0 +1,152 @@ +package main + +import "core:fmt" +import "core:os" +import "core:strings" +import "core:path/filepath" +import "core:c/libc" +import "../ltc" + +/* +TODO: +- Automatically detect if timecode is in left or right channel +- Better error when specifying a pure folder, or handle it automatically +*/ + +temp_audio_file_name :: "temp.raw" + +sample_rate :: 48000 +frame_rate := i32(25) + +main :: proc() { + fmt.println("ChirpSync - Copyright (C) 2024 Sander J. Skjegstad\nThis program comes with ABSOLUTELY NO WARRANTY.\nThis is free software, and you are welcome to redistribute it\nunder certain conditions. See the GPLv3 for details.") + arguments := os.args[1:] + input := arguments[0] + output := arguments[1] + + + input_files, err_in_list := filepath.glob(input) + if err_in_list == .Syntax_Error { fmt.println("ERROR: Syntax_Error in input.") + os.exit(1) } + fmt.printfln("%#v", input_files) + + + output_info, erro := os.lstat(output) + if erro != os.ERROR_NONE { fmt.println("ERROR: Output invalid.") + os.exit(1) } + fmt.printfln("%#v", output_info) + if !output_info.is_dir { fmt.println("ERROR: Output needs to be a directory.") + os.exit(1) } + + for input_file, index in input_files { + + fmt.println("\n\n - - - File", index, "- - -\n", input_file, "\n\n") + + fmt.println("\n\nExtracting audio...\n\n") + os.remove(temp_audio_file_name) + audio_extract_command := fmt.caprintf("ffmpeg -y -loglevel fatal -i %v -ss 0 -t 12 -f u8 -hide_banner -map_metadata -1 -filter_complex \"[0:a]channelsplit=channel_layout=stereo:channels=FL[left]\" -map \"[left]\" -acodec pcm_u8 %v", + input_file, temp_audio_file_name) + fmt.printf("Command: %v\n\n", audio_extract_command) + libc.system(audio_extract_command) + + + + fmt.println("\n\nLoading audio...") + raw_audio_handle, errra := os.open(temp_audio_file_name) + if errra != os.ERROR_NONE { + fmt.println("ERROR: Could not get file handle for raw audio.") + os.exit(1) + } + raw_audio, erra := os.read_entire_file_from_handle(raw_audio_handle) + if !erra { + fmt.println("ERROR: Could not read raw audio file.") + os.exit(1) + } + + + + fmt.println("\nDecoding LTC...\n") + + decoder := ltc.decoder_create(sample_rate/frame_rate, 128) + + address := 0xA0 + for ltc.decoder_queue_length(decoder)<24 && address=sample_rate*10 { + fmt.println("WARNING: Failed to find timecode in:", input_file) + continue + } + + avg_sum : i64 = 0 + prev_item : ltc.FrameExt = {} + for item, i in decoder.queue[0:10] { + fmt.printf("%03d - ", i) + print_ltc(item) + fmt.printf("\n") + if i>0 do avg_sum += item.off_start - prev_item.off_start + prev_item = item + } + average_frame_distance := avg_sum/9 + fmt.println("Avg. frame distance:", average_frame_distance) + + initial_timecode := decoder.queue[0] + + tv_standard : ltc.TV_STANDARD = .TV_625_50 + switch { + case frame_rate % 30 == 0: + tv_standard = .TV_525_60 + case frame_rate % 25 == 0: + tv_standard = .TV_625_50 + case frame_rate % 24 == 0: + tv_standard = .TV_FILM_24 + } + + output_timecode := initial_timecode + for output_timecode.off_start >= 0 { + fmt.println("Reconstructing timecode at start point... @", output_timecode.off_start) + ltc.frame_decrement(&output_timecode.ltc, frame_rate, tv_standard, {.TC_CLOCK, .BGF_DONT_TOUCH}) + output_timecode.off_start -= average_frame_distance + } + + fmt.print("\nFinal timecode: ") + print_ltc(output_timecode) + fmt.print("\n") + + ltc.decoder_free(decoder) + + + + fmt.println("\nWriting new file...\n") + + timecode_string := timecode_to_string(output_timecode) + output_command := fmt.caprintf("ffmpeg -hide_banner -y -i %v -metadata:s timecode=%v -map 0:a -map 0:v -movflags use_metadata_tags -acodec copy -vcodec copy -metadata:s timecode=%v -timecode %v %v%v", + input_file, + timecode_string, + timecode_string, + timecode_string, + output_info.fullpath, + filepath.base(input_file)) + fmt.println("Command:", output_command, "\n") + libc.system(output_command) + + os.close(raw_audio_handle) + } +} + +print_ltc :: proc(f : ltc.FrameExt) { + timecode_string := timecode_to_string(f) + fmt.printf("@%07d - TC: %v", + f.off_start, + timecode_string,) +} + +timecode_to_string :: proc(f : ltc.FrameExt) -> string { + return fmt.aprintf("%02d:%02d:%02d:%02d", + f.ltc.hours_tens * 10 + f.ltc.hours_units, + f.ltc.mins_tens * 10 + f.ltc.mins_units, + f.ltc.secs_tens * 10 + f.ltc.secs_units, + f.ltc.frame_tens * 10 + f.ltc.frame_units,) +} \ No newline at end of file -- cgit v1.2.1