Config Package¶
The config package manages application configuration, including persistence to disk.
Package Location¶
Responsibility¶
- Load and save configuration
- Provide default values
- Manage file paths
- Store YouTube credentials
Key Files¶
| File | Purpose |
|---|---|
config.go |
Configuration loading/saving |
config_test.go |
Unit tests |
Key Types¶
Config¶
Main configuration structure:
type Config struct {
Topics []models.Topic `json:"topics"`
DefaultPresenter string `json:"default_presenter"`
LogoDirectory string `json:"logo_directory"`
BgColor string `json:"bg_color"` // Background color for vertical video lower third
YouTube YouTubeConfig `json:"youtube"`
Recording RecordingDefaults `json:"recording"`
}
YouTubeConfig¶
YouTube API credentials:
type YouTubeConfig struct {
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
Expiry time.Time `json:"expiry"`
DefaultPrivacy string `json:"default_privacy"`
DefaultPlaylistID string `json:"default_playlist_id"`
DefaultPlaylistName string `json:"default_playlist_name"`
}
RecordingPresets¶
Recording presets saved between sessions (used by systray quick-record):
type RecordingPresets struct {
RecordAudio bool `json:"record_audio"`
RecordWebcam bool `json:"record_webcam"`
RecordScreen bool `json:"record_screen"`
VerticalVideo bool `json:"vertical_video"`
AddLogos bool `json:"add_logos"`
Topic string `json:"topic,omitempty"`
}
PresetsConfigured¶
The Config struct includes a PresetsConfigured bool field that distinguishes "never configured" from "configured with all toggles off." This is used by the systray to detect first-run and prompt the user to configure recording presets before their first recording.
When PresetsConfigured is false, the systray StartRecording() method blocks recording and opens the TUI to the presets configuration section instead. After the user saves, PresetsConfigured is set to true and subsequent recordings use the saved presets.
GifLoopMode¶
Animated GIF behavior:
type GifLoopMode string
const (
GifLoopContinuous GifLoopMode = "continuous"
GifLoopOnceStart GifLoopMode = "once_start"
GifLoopOnceEnd GifLoopMode = "once_end"
)
Core Functions¶
Load¶
Load configuration from disk:
Behavior:
- Determine config file path
- Read file if exists
- Parse JSON
- Apply defaults for missing values
- Return config
Save¶
Persist configuration to disk:
Behavior:
- Marshal to JSON (indented)
- Create directory if needed
- Write to file atomically
- Set appropriate permissions
GetDefaultVideosDir¶
Get output directory:
Returns: ~/Videos/Screencasts
GetCurrentRecordingNumber¶
Get next episode number:
File Paths¶
Configuration¶
PID Files (Runtime)¶
Output Directory¶
Path Constants¶
const (
VideoPIDFile = "/tmp/kvp-video-pid"
AudioPIDFile = "/tmp/kvp-audio-pid"
WebcamPIDFile = "/tmp/kvp-webcam-pid"
StatusFile = "/tmp/kvp-status"
OutputDirFile = "/tmp/kvp-output-dir"
)
Default Values¶
func defaultConfig() *Config {
return &Config{
Topics: models.DefaultTopics(),
DefaultPresenter: "",
LogoDirectory: "",
YouTube: YouTubeConfig{
DefaultPrivacy: "unlisted",
},
Recording: RecordingDefaults{
RecordAudio: true,
RecordScreen: true,
RecordWebcam: false,
AddLogos: false,
TitleColor: "#FF9500",
},
}
}
JSON Format¶
{
"topics": [
{
"name": "QGIS sketches",
"color": "#FF9500"
},
{
"name": "GIS development tutorials",
"color": "#0066CC"
}
],
"default_presenter": "Tim Sketcher",
"logo_directory": "/home/user/Pictures/logos",
"youtube": {
"client_id": "xxx.apps.googleusercontent.com",
"client_secret": "xxx",
"access_token": "ya29.xxx",
"refresh_token": "1//xxx",
"expiry": "2024-01-15T12:00:00Z",
"default_privacy": "unlisted",
"default_playlist_id": "PLxxx",
"default_playlist_name": "My Playlist"
},
"recording": {
"record_audio": true,
"record_webcam": false,
"record_screen": true,
"vertical_video": false,
"add_logos": true,
"title_color": "#FF9500"
}
}
Error Handling¶
First Run (No Config)¶
func Load() (*Config, error) {
path := getConfigPath()
data, err := os.ReadFile(path)
if os.IsNotExist(err) {
// Return defaults for new users
return defaultConfig(), nil
}
if err != nil {
return nil, err
}
// Parse existing config
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, err
}
return &cfg, nil
}
Atomic Save¶
func Save(cfg *Config) error {
path := getConfigPath()
// Create directory
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
// Marshal config
data, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return err
}
// Write to temp file first
tmpFile := path + ".tmp"
if err := os.WriteFile(tmpFile, data, 0600); err != nil {
return err
}
// Atomic rename
return os.Rename(tmpFile, path)
}
XDG Compliance¶
func getConfigPath() string {
configDir := os.Getenv("XDG_CONFIG_HOME")
if configDir == "" {
home, _ := os.UserHomeDir()
configDir = filepath.Join(home, ".config")
}
return filepath.Join(configDir, "kartoza-screencaster", "config.json")
}
func GetDefaultVideosDir() string {
videosDir := os.Getenv("XDG_VIDEOS_DIR")
if videosDir == "" {
home, _ := os.UserHomeDir()
videosDir = filepath.Join(home, "Videos")
}
return filepath.Join(videosDir, "Screencasts")
}
Testing¶
func TestLoad_NoFile(t *testing.T) {
// Use temp directory
os.Setenv("XDG_CONFIG_HOME", t.TempDir())
cfg, err := Load()
assert.NoError(t, err)
assert.NotNil(t, cfg)
assert.Equal(t, 4, len(cfg.Topics)) // Defaults
}
func TestSave_CreatesDirectory(t *testing.T) {
tmpDir := t.TempDir()
os.Setenv("XDG_CONFIG_HOME", tmpDir)
cfg := &Config{Topics: []models.Topic{{Name: "Test"}}}
err := Save(cfg)
assert.NoError(t, err)
assert.FileExists(t, filepath.Join(tmpDir, "kartoza-screencaster", "config.json"))
}
Usage Example¶
// Load config
cfg, err := config.Load()
if err != nil {
log.Fatal(err)
}
// Modify
cfg.DefaultPresenter = "New Name"
cfg.Recording.AddLogos = true
// Save
if err := config.Save(cfg); err != nil {
log.Fatal(err)
}
// Access paths
outputDir := config.GetDefaultVideosDir()
fmt.Println("Videos saved to:", outputDir)
Related Packages¶
- models - Topic and other shared types
- youtube - Uses YouTubeConfig for auth
- recorder - Uses paths and defaults