aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSan Jacobs2024-05-14 05:11:43 +0200
committerSan Jacobs2024-05-14 05:11:43 +0200
commit07f9ad8350df6cd87ed68f9d6d943c769c2c4007 (patch)
tree001355a1568fdb03bb8a52bfd6711dc26f915c00 /src
downloadchirpsync-07f9ad8350df6cd87ed68f9d6d943c769c2c4007.tar.gz
chirpsync-07f9ad8350df6cd87ed68f9d6d943c769c2c4007.tar.bz2
chirpsync-07f9ad8350df6cd87ed68f9d6d943c769c2c4007.zip
Wrote the whole thing in a day
Diffstat (limited to 'src')
-rw-r--r--src/main.odin152
1 files changed, 152 insertions, 0 deletions
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 {
+ // @BUG: Has major potential to go out of bounds
+ ltc.decoder_write(decoder, &raw_audio[address], sample_rate, 0)
+ address += sample_rate;
+ }
+ if 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