aboutsummaryrefslogtreecommitdiff
path: root/src/main.odin
blob: 90d3ec6d246076db4ac6abc00141d7b8a6746c2b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
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
- Mute channel containing timecode
- 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,)
}