# CLI<no value>

## Commands

djctl provides several commands used to control its general operation. The following section provide an overview of each command and any accompanying flags.

{{< callout tip >}}
For general djctl usage, the most important command of interest will be the [start](/docs/reference/cli/#start) command.
{{< /callout >}}

### completion

djctl can generate a shell completion file for bash, fish, powershell, and zsh. For detailed information on how to configure shell completion for a specific shell, please use the `help` command.

For example, use the following command for bash instructions:

```sh
$ ./djctl help completion bash
```

Example output:

```sh
Generate the autocompletion script for the bash shell.

This script depends on the 'bash-completion' package.
If it is not installed already, you can install it via your OS's package manager.

To load completions in your current shell session:

        source <(djctl completion bash)

To load completions for every new session, execute once:

#### Linux:

        djctl completion bash > /etc/bash_completion.d/djctl

#### macOS:

        djctl completion bash > /usr/local/etc/bash_completion.d/djctl

You will need to start a new shell for this setup to take effect.
```

### help

Built-in help for commands and flags. Provides help for any command in the application.

Example usage:

```sh
$ ./djctl help start
```

Example output:

```sh
Observe and output track play events

Usage:
  djctl start [flags]

Flags:
      --art.precedence string               art source precedence (default "native,spotify,discogs,placeholder")
      --cue.filename string                 featured track cue sheet file
      --cue.header.file string              FILE header field
      --cue.header.filetype string          FILE format
      --cue.header.performer string         PERFORMER header field
      --cue.header.title string             TITLE header field
      --delay uint                          delay in seconds before track is declared featured
      --denon.discovery.ignore_link_local   ignore link-local addresses in Denon discovery
      --denon.discovery.max_devices uint    maximum number of devices to discover (0 for unlimited)
      --denon.discovery.timeout duration    Denon discovery timeout (default 5s)
      --denon.token string                  Denon token (hex) (default "52fdfc072182654f163f5f0f9a621d72")
      --detection.algorithm string          featured track detection algorithm (default "volume")
      --detection.volume.percent uint       volume detection algorithm percent (default 100)
      --discogs.key string                  Discogs consumer key
      --discogs.secret string               Discogs consumer secret
      --discord.channel string              Discord channel ID
      --discord.format string               Discord message format string (default "$ARTIST$ - $SONG$")
      --discord.token string                Discord API token
      --djuced.filename string              DJUCED text output file
      --dummy.interval uint                 Dummy track generator interval between transitions (default 5)
      --extract.regex.artist string         extract artist using named "artist" regex capture group
      --extract.regex.title string          extract title using named "title" regex capture group
  -h, --help                                help for start
      --hide.color string                   hide track when deck set to this color
      --hide.regex.artist string            hide track when regex matches artist
      --hide.regex.filename string          hide track when regex matches filename
      --hide.regex.genre string             hide track when regex matches genre
      --hide.regex.title string             hide track when regex matches title
      --history.filename string             featured track history file
      --history.format string               history line format string (default "$ARTIST$ - $SONG$")
      --http.addr string                    http listen address (default ":9090")
      --mediacontrol.binarypath string      path to media-control binary (default "/opt/homebrew/bin/media-control")
      --mediacontrol.bundleid string        bundle ID filter for media-control source (e.g. com.spotify.client); empty means no filter
      --mediacontroltcp.addr string         TCP source server listener address for media-control (default ":9191")
      --mediacontroltcp.bundleid string     bundle ID filter for media-control TCP source (e.g. com.spotify.client); empty means no filter
      --osc.address.artist string           OSC address for artist
      --osc.address.song string             OSC address for song
      --osc.host string                     OSC host
      --osc.port uint                       OSC port (default 7000)
      --outfile.art string                  featured track album art png file
      --outfile.featured string             featured track file
      --outfile.format string               file format string (default "$ARTIST$ - $SONG$")
      --placeholder.art.internal string     album art placeholder internal image name
      --source string                       track source (default "denon")
      --spotify.barcolor string             Spotify code bar color (default "white")
      --spotify.bgcolor string              Spotify code background color (default "000000")
      --spotify.id string                   Spotify ID
      --spotify.secret string               Spotify secret
      --spotify.timeout uint                Spotify code creation timeout in seconds (default 4)
      --spotify.width uint                  Spotify code width (default 256)
      --spotifysource.id string             Spotify source client ID
      --spotifysource.refresh string        Spotify source refresh token (run "djctl spotify setup" to obtain one)
      --spotifysource.secret string         Spotify source client secret
      --target.device string                target device name
      --target.software string              target software name
      --theme.dir string                    custom theme directory
      --theme.internal string               internal theme name (default "default")
      --twitch.api_key string               Twitch extension API key
      --twitch.format string                Twitch message format string (default "$ARTIST$ - $SONG$")
      --webhook.format string               custom field format string (default "$ARTIST$ - $SONG$")
      --webhook.json.art strings            album art key path in JSON webhook payload (default [art])
      --webhook.json.artist strings         artist key path in JSON webhoook payload (default [artist])
      --webhook.json.duration strings       duration key path in JSON webhoook payload (default [duration])
      --webhook.json.elapsed strings        elapsed time key path in JSON webhoook payload (default [elapsed])
      --webhook.json.epoch strings          epoch timestamp key path in JSON webhoook payload (default [timestamp_epoch])
      --webhook.json.format strings         custom format field key path in JSON webhoook payload
      --webhook.json.genre strings          genre key path in JSON webhook payload (default [genre])
      --webhook.json.spotify strings        Spotify code key path in JSON webhook payload (default [spotify_code])
      --webhook.json.timestamp strings      timestamp key path in JSON webhoook payload (default [timestamp])
      --webhook.json.title strings          title key path in JSON webhook payload (default [title])
      --webhook.url string                  featured track webhook url

Global Flags:
      --conf string          path to the configuration file
      --license.key string   license key
      --log.file string      log to filename
      --log.format string    one of text or json (default "text")
      --log.level string     one of debug, info, warn, or error (default "info")
```

### license

Decodes and displays information about a djctl license.

Example usage:

```sh
$ ./djctl license --license.key="FT7YCAYB...<REDACTED>"
```

Example output:

```sh
Registered to: Trusted Tester <placeholder@placeholder.com>
Version: 1
Expires: 2066-05-30
```

### probe

Performs StagelinQ discovery, searching for compatible Denon devices on the network. When discovered, connects to the StateMap service on the device and attempts to dump supported StateMap values.

Example usage:

```sh
$ ./djctl probe --log.level=debug --log.file=probe.txt
```

In the above example, because the `--log.file` flag was used, no output appears on the terminal. Instead, after several minutes, the command line prompt will return and the `probe.txt` file will contain diagnostic information useful to the developer.

### start

Launches djctl in server mode. This is the primary mode of operation.

The following track transition sources are supported:

- [Denon](/docs/quickstart/denon/)
- [DJUCED](/docs/quickstart/djuced/)
- Dummy
- [MediaRemote](/docs/quickstart/mediaremote/) (macOS) - most macOS media players
- [Serato](/docs/quickstart/serato) (experimental)
- [Spotify](/docs/quickstart/spotify) (Spotify Premium required)

Denon is the default source if the `--source` flag is not used during launch.

The following "featured track" sinks/destinations are supported:

- Discord
- File
- OSC
- Twitch
- Webhook
- WebSocket

Most users will interact with the WebSocket sink indirectly by way of displaying a built-in "theme" in an OBS browser source window. It is always enabled.

The following section details notable features and flags used to control the behavior of the `start` command.

#### Discovery (Denon)

For most users, djctl will automatically detect Denon Stagelinq devices without issue. However, in networks where a Denon device is slow to receive a DHCP address, there may be a brief period where the device announces itself using a link local (169.254.x.x) address.

When djctl is running on a machine directly connected to the Denon device (i.e., no switch), this isn't problematic as both devices will use link local addressing in the absence of static addressing and/or DHCP. However, if the djctl machine and Denon device are connected via a switch with DHCP available, the link local address advertised by the Denon will be unreachable.

To address this, use the `--denon.discovery.ignore_link_local` flag to ignore temporary link local broadcast messages from the Denon device. Once the Denon device receives a DHCP address, its broadcast messages will contain a reachable DHCP-assigned address.

For slow DHCP scenarios, it's recommended to extend the discovery timeout from the default 5 seconds to 2 minutes using `--denon.discovery.timeout=2m`.

To avoid waiting the full 2 minutes, you can use `--denon.discovery.max_devices` to exit discovery once a certain number of Stagelinq devices are detected. For Prime controller users, try `--denon.discovery.max_devices=3`. This allows detection of two `OfflineAnalyzer` and one `JC11` devices, as typically observed in the logs:

```log
time=2025-02-13T15:34:32.976-08:00 level=INFO msg="Stagelinq device detected" deviceIP=192.168.68.198 deviceName=prime4 deviceSoftwareName=OfflineAnalyzer deviceSoftwareVersion=1.0.0
time=2025-02-13T15:34:32.976-08:00 level=INFO msg="Stagelinq device detected" deviceIP=192.168.68.198 deviceName=prime4 deviceSoftwareName=OfflineAnalyzer deviceSoftwareVersion=1.0.0
time=2025-02-13T15:34:32.976-08:00 level=INFO msg="Stagelinq device detected" deviceIP=192.168.68.198 deviceName=prime4 deviceSoftwareName=JC11 deviceSoftwareVersion=4.2.0
```
##### Token (Denon)

The Stagelinq discovery process includes creating an identity token. Previously, djctl generated this token randomly, which was generally effective. However, the current version of djctl uses a pre-defined, known-good token to ensure consistent and reliable discovery.

If users wish to revert to the older random token generation behavior, they can use the `--denon.token=""` flag (setting it to empty).

#### Featured track algorithms (Denon)

The `--detection.algorithm` flag allows selecting the Denon featured track detection algorithm. By default, this algorithm defaults to `volume`. The other available algorithm is `lead`.

The `volume` algorithm promotes a track to "featured" state when it is the only audible track present in the mix. Other tracks that may have been playing during a transition must no longer be present in the mix. The volume threshold that defines whether a track is still in the mix is configurable using the `--detection.volume.percent` flag. When lowered from its default of `100`, a track can become "featured" even when outgoing faders are still partially up. As long as all the other faders have dropped below a threshold (calculated as a percentage of the loudest track in the mix), a new track can be promoted to "featured." To use this algorithm, set the `--detection.algorithm=volume` flag. Please note that this is the default algorithm.

The `lead` algorithm follows the active play state on a deck. A track is lead if you press play and no other track is also in the play state. When you engage play on a new track, this new track doesn't transition into lead state until the other track has been stopped. To use this algorithm, set the `--detection.algorithm=lead` flag.

The `altlead` algorithm is similar to the `lead` algorithm. It is intended as an experimental approach to support SC players without an X series mixer. To use this algorithm, set the `--detection.algorithm=altlead` flag.

#### Track delay

The `--delay` flag provides a delayed "hold" or "queue" mechanism for tracks before publishing to a "featured track" sink.

Set this to a non-zero value in order to avoid momentary track cuts triggering a published track transition event. If you cut the fader to perform "drops" or cut mixing and scratching, you will likely want to adjust this value.

For example, setting `delay` to `3` would allow you to rapidly toggle the crossfader back and forth between channels without triggering a track transition. Instead, each new cut between channels preempts the last detected transition and resets the timer. Once no further transitions are detected within the 3 second window, the last detected transition is published to the "featured track" sink(s).

#### "Clean" metadata extraction

djctl can attempt to extract "clean" metadata through the use of regular expression capture groups. This is supported for both the title and artist metadata using the corresponding `--extract.regex.title` and `--extract.regex.artist` flags.

{{< callout caution >}}
Regular expressions are an advanced topic which will require non-trivial user effort to master. You have been warned.
{{< /callout >}}

When extracting a title, the capture group must be named `title` and similarly when extracting an artist, it must be named `artist`. No capture group names besides `title` and `artist` are detected by djctl.

The `title` capture group name is expected when using `--extract.regex.title` and similarly the `artist` capture group name is expected when using `--extract.regex.artist`. djctl will not launch if you supply either of these flags without the required capture group name present in the regular expression.

{{< callout note >}}
Capture group names are case sensitive. Use all lowercase; for example, <mark>?P&lt;title&gt;</mark> and <u>not</u> <mark>?P&lt;Title&gt;</mark>.
{{< /callout >}}

The following example attempts to extract a "clean" track title by ignoring track number, Camelot key, BPM, and file extension:

```text
--extract.regex.title='^(?P<num>\d+)?\.?\s?(?P<title>.+?)\s?-?\s?(?P<key>\d{1,2}[a-zA-Z])?\s?-?\s?(?P<bpm>\d{1,3})?(\.(?P<extension>\w+))?$'
```

Please note the surrounding single quotes; this is important and should prevent the shell from escaping special characters.

The following table illustrates an original title and an extracted title using the aforementioned example regular expression.

|Original|Extracted|
|-|-|
|03. Eli Brown - Escape (Original Mix) - 7A - 127.WAV|Eli Brown - Escape (Original Mix)|
|Eli Brown - Escape (Original Mix) - 7A - 127.WAV|Eli Brown - Escape (Original Mix)|
|- Escape (Original Mix) - 7A - 127.WAV|- Escape (Original Mix)|
|The Space Between Feat. Laura Aqui (Assaf Remix) - 1A - 132|The Space Between Feat. Laura Aqui (Assaf Remix)|
|The Space Between Feat. Laura Aqui (Assaf Remix) - 1A -|The Space Between Feat. Laura Aqui (Assaf Remix)|
|The Space Between Feat. Laura Aqui (Assaf Remix) - 1A|The Space Between Feat. Laura Aqui (Assaf Remix)|
|The Space Between Feat. Laura Aqui (Assaf Remix) -|The Space Between Feat. Laura Aqui (Assaf Remix)|
|The Space Between Feat. Laura Aqui (Assaf Remix)|The Space Between Feat. Laura Aqui (Assaf Remix)|
{.table-striped}

When developing and testing regular expression capture group extractions, please use the [regex101.com](https://regex101.com/) web site in Golang mode. This will use the same extraction engine djctl uses internally.

{{< callout caution >}}
Take caution with this feature as it is difficult to write a regular expression that can handle all possible cases with accuracy. Your time may be better spent fixing the actual track metadata.
{{< /callout >}}

#### Hiding ("DJ AM" mode) (Denon)

djctl supports two modes of track hiding. The simplest and most versatile is using the `--hide.color` flag. The second and more advanced method is regular expression matching using the `--hide.regex.artist`, `--hide.regex.title`, `--hide.regex.genre`, and `--hide.regex.filename` flags.

##### Color (Denon)

If you would like the ability to dynamically hide tracks while DJing, use the `--hide.color` flag. When the EngineOS deck color matches the specified `--hide.color` flag value, djctl will hide the track information. When it no longer matches, the track information is revealed. You can toggle this color change while DJing, forcing a track to dynamically hide and unhide.

{{< callout note >}}
You can change the color of a deck using the &quot;Deck Colors&quot; screen within the EngineOS &quot;User Profile&quot; settings.
{{< /callout >}}

The following color names are recognized:

- red
- purple
- red
- orange
- yellow
- limegreen
- green
- lightblue
- blue

##### Regular expression (Denon)

{{< callout caution >}}
Regular expressions are an advanced topic which will require non-trivial user effort to master. You have been warned.
{{< /callout >}}

You can also specify a regular expression to trigger track hiding with the `--hide.regex.artist`, `--hide.regex.title`, `--hide.regex.genre`, and `--hide.regex.filename` flags.

For example, you can automatically hide tracks whose track title metadata contains "[ID]" at the beginning with the following flag:

```text
--hide.regex.title='^\[ID\].*'
```

Please note that when any of the `--hide.regex.*` flags encounter a match, all track metadata (title, artist, and art) will become hidden.

#### HTTP listener

The djctl `start` command will automatically launch an HTTP listener on port 9090. If you need to change this for any reason, you can use the `--http.addr` flag.

By default, the listener will bind to all addresses. You can restrict this to a specific address such as localhost. For example:

```sh
$ ./djctl start --http.addr=127.0.0.1:9090
```

This may be preferred when operating on a "hostile" network that isn't under your full control.

Please see the [WebSocket API documentation](/docs/reference/websocket-api/) for more information.

#### File outputs

djctl can sink featured track information to a file. The `--outfile.featured` string should contain the file name destination of the featured track sink. This file will be overwritten each time a track transition event occurs. If this string is empty, no file sink updates will occur. Use the `--outfile.featured` flag to enable this feature along with `--outfile.format` to control the contents of the featured track file. Please see the [substitution variables](#substitution-variables) section for more information about constructing format specifiers.

To output album art, specify an `--outfile.art` file location. The album art is always a 256x256 resolution PNG file. When no album art is unavailable, a 1-pixel transparent PNG file is written instead.

To output a historical log of tracks played, use the `--history.filename` and `--history.format` flags. During each track transition, a line will be appended to the file specified in `--history.filename`. Use the `--history.filename` flag to enable this feature along with `--history.format` to control the contents of the history file. Please see the [substitution variables](#substitution-variables) section for more information about constructing format specifiers.

##### Substitution variables

djctl supports a basic substitution mechanism where variable strings such as `$ARTIST$` and `$SONG$` are replaced with artist and title metadata respectively. The default behavior uses `$ARTIST$ - $SONG$` as the format string for both `--outfile.format` and `--history.format` flags. With this format string, the song "Just Can't Get Enough" by the artist "Depeche Mode" outputs `Depeche Mode - Just Can't Get Enough`.

The following table describes the substitution variables available for use with the `--outfile.format`, `--history.format`, `--discord.format`, `--twitch.format`, and `--webhook.format` flags.

| Variable | Description | Example output |
|-----------|------|-------------|
| `$ARTIST$` | Track artist | Depeche Mode |
| `$SONG$` | Track title | Just Can't Get Enough |
| `$GENRE$` | Track genre | New Wave |
| `$TIMESTAMP_YEAR$` | Year component of current time | 2022 |
| `$TIMESTAMP_MONTH$` | Zero-padded month component of current time | 03 |
| `$TIMESTAMP_DAY$` | Zero-padded day component of current time | 01 |
| `$TIMESTAMP_HOUR$` | Zero-padded hour component of current time | 09 |
| `$TIMESTAMP_MINUTE$` | Zero-padded minute component of current time | 22 |
| `$TIMESTAMP_SECOND$` | Zero-padded second component of current time | 49 |
| `$ELAPSED$` | Elapsed time since first track in current history | 00:01:24 |
{.table-striped}

The following table provides example format strings along with example output.

| Format string | Example output |
|-----------|------|
| `$ARTIST$ - $SONG$` | Depeche Mode - Just Can't Get Enough | Depeche Mode |
| `$TIMESTAMP_HOUR$:$TIMESTAMP_MINUTE$:$TIMESTAMP_SECOND$ - $ARTIST$ - $SONG$` | 20:07:43 - Depeche Mode - Just Can't Get Enough| 09 |
{.table-striped}

#### Discord

{{< callout note >}}
This is an experimental feature.
{{< /callout >}}

djctl can sink featured track information to a Discord channel. Use the `--discord.token`, `--discord.channel`, and `--discord.format` flags to configure. This documentation will be updated at a later date with detailed setup instructions.

#### Twitch

{{< callout note >}}
This is an experimental feature available to licensed users only.
{{< /callout >}}

Activate the [Twitch extension](https://dashboard.twitch.tv/extensions/u9wbnrygf06s7l3hb66p41upggylyg) on your Twitch channel.

Please see the [Twitch setup](#twitch-1) section of the documentation for information on acquiring the API key required to configure this integration.

djctl can sink featured track information to the djctl Twitch extension. Use the `--twitch.api_key` and `--twitch.format` flags to configure.

This documentation will be updated at a later date with detailed setup instructions. Please join the Discord support chat for "white glove" assistance setting this up.

#### Open Sound Control (OSC)

{{< callout note >}}
This is an experimental feature.
{{< /callout >}}

Documentation coming soon.

#### CUE sheet

{{< callout note >}}
This is an experimental feature.
{{< /callout >}}

To facilitate [uploading of track listing data to Mixcloud](https://help.mixcloud.com/hc/en-us/articles/360004053959-How-do-I-manually-add-a-tracklist-to-my-shows), djctl can perform CUE sheet generation.

Use `--cue.filename` to output a CUE formatted track log to the specified file. To supply CUE header fields, use `--cue.header.file`, `--cue.header.filetype`, `--cue.header.performer`, and `--cue.header.title`.

For example, the following flags will generate a CUE sheet file.

```text
--cue.filename=mix.cue \
--cue.header.file="Matt in the Mix.mp3" \
--cue.header.filetype="MP3" \
--cue.header.performer="DJ Matt" \
--cue.header.title="Matt in the Mix"
```

The following is an example CUE sheet generated with djctl.

```text
PERFORMER "DJ Matt"
TITLE "Matt in the Mix"
FILE "Matt in the Mix.mp3" MP3
  TRACK 01 AUDIO
    TITLE "Where Are You Now - Kungs Remix"
    PERFORMER "Lost Frequencies"
    INDEX 01 00:00:00
  TRACK 02 AUDIO
    TITLE "I've Been Thinking About You - Klaas Remix"
    PERFORMER "Klaas"
    INDEX 01 00:13:00
  TRACK 03 AUDIO
    TITLE "Ice Life - Nu Disco Mix"
    PERFORMER "Lissat"
    INDEX 01 00:17:00
  TRACK 04 AUDIO
    TITLE "Acid Flex"
    PERFORMER "Andy Buchan"
    INDEX 01 00:22:00
```

Please note, the 00:00:00 time marker starts when the first featured track is observed.

#### Discogs

{{< callout note >}}
This is an experimental feature available to licensed users only.
{{< /callout >}}

For users with a Discogs account, djctl can enrich track metadata with album art from their extensive, community-maintained music database. The integration currently leverages the [Discogs Auth Flow](https://www.discogs.com/developers/#page:authentication,header:authentication-discogs-auth-flow) which requires the creation of a "Consumer Key" and "Consumer Secret". To generate these credentials, perform the following steps:

* Visit [https://www.discogs.com/settings/developers](https://www.discogs.com/settings/developers).
* Authenticate with your Discogs account.
* Click "Create an application".
* Enter "djctl" in the "Application Name" field.
* Enter "dj control" in the "Description" field.
* Click "Create application" to submit.

Once created, the "Consumer Key" and "Consumer Secret" are displayed. These values will need to be passed to djctl as parameters using the `--discogs.key` and `--discogs.secret` flags. Both are required to successfully authenticate to the Discogs API.

{{< callout caution >}}
Treat the Consumer Key and Consumer Secret as credentials. Never share them with anyone.
{{< /callout >}}

Discogs album art is cached for one hour. Practically speaking, this allows djctl to avoid excessive requests to the Discogs API. It also means that djctl memory consumption will slowly grow during long DJ sessions as the cache grows.

Once the Discogs integration is configured, use the `--art.precedence` flag to control the [art source order of preference](#art-precedence).

#### Spotify Metadata

{{< callout note >}}
This is an experimental feature available to licensed users only.
{{< /callout >}}

For users with access to a Spotify Premium account, djctl can augment track metadata with a [Spotify Code](https://www.spotifycodes.com/) and high quality album art. The integration currently leverages the [client credentials authentication flow](https://developer.spotify.com/documentation/general/guides/authorization/client-credentials/) which requires a Client ID and Client Secret to generate authentication tokens. To create a Spotify Client ID and Client Secret, follow these steps:

* Visit [https://developer.spotify.com/dashboard/applications](https://developer.spotify.com/dashboard/applications).
* Authenticate with your Spotify Premium account.
* Click "Create an app".
* Enter "djctl" in the "App name" field.
* Enter "dj control" in the "App description" field.
* Select the checkboxes and submit.

Once created, the "Client ID" is displayed. Click on "Show client secret" to reveal the "Client secret" field. These values will need to be passed to djctl as parameters using `--spotify.id` and `--spotify.secret`. Both are required to successfully authenticate to the Spotify API.

{{< callout caution >}}
Treat the client ID and secret as credentials. Never share them with anyone.
{{< /callout >}}

The Spotify Code visual format can be customized using the `--spotify.barcolor`, `--spotify.bgcolor`, and `--spotify.width` parameters. The default Spotify Code is a white bar code on a black background. The image width is 256 pixels in order to match the width of the track art. You are free to override these values to match other use cases and aesthetics.

For the `--spotify.barcolor` option, either the value `white` or `black` are valid. The `--spotify.bgcolor` option should be specified as a [three digit hex representation of an RGB color](https://www.hexcolortool.com/). Please omit the `#` character; for example, the color `#0099cc` should be passed as `--spotify.bgcolor=0099cc`. The width of the image can also be specified using the `--spotify.width` parameter. The height is not configurable and is a fixed ratio based upon the width.

Because the generation of a Spotify Code requires several API calls to remote systems, a default timeout of 4 seconds is enforced. This can be overriden with the `--spotify.timeout` parameter.

All Spotify Codes and album art are cached for one hour. Practically speaking, this allows djctl to avoid excessive requests to the Spotify API. It also means that djctl memory consumption will slowly grow during long DJ sessions as the cache grows.

Currently, the `SliderSpotify` theme supports display of Spotify Codes. To enable this theme, use the `--theme.dir=./themes/SliderSpotify/` option.

Once the Spotify integration is configured, use the `--art.precedence` flag to control the [art source order of preference](#art-precedence).

#### Art Precedence

By default, the track art is selected based upon the following priority, ordered from highest preference to lowest preference:

|Name|Description|
|----|-----------|
|native|Track art as provided by the DJ hardware or software.|
|spotify|Track art as discovered via Spotify API. Requires configuration of the [Spotify integration](#spotify).|
|discogs|Track art as discovered via Discogs API. Requires configuration of the [Discogs integration](#discogs).|
|placeholder|Static placeholder image. Requires configuration of an [art placeholder](#placeholders).|
{.table-striped}

To customize this order, use the `--art.precedence` configuration option. For example, to prefer Discogs art over Spotify art, use `--art.precedence=native,discogs,spotify,placeholder`. To ignore native art and only ever use Spotify art, use `--art.precedence=spotify`.

{{< callout note >}}
If an track art source is unconfigured but specified in the `--art.precedence` list, it is ignored.
{{< /callout >}}

#### Targets (Denon)

If you have multiple Denon devices present on your network, you may wish to use the `--target.device` and `--target.software` flags to target a specific device. In most cases, you will never find the need to use this flag.

For example, the Denon Prime 4 presents itself as the `prime4` device running software `JC11`. See the example below for an example of how to target this device when launching djctl.

```sh
$ ./djctl start --target.device=prime4 --target.software=JC11
```

{{< callout note >}}
Device and software names are case sensitive.
{{< /callout >}}

#### Themes

A small number of themes are embedded within the djctl binary. You can switch between them using the `--theme.internal` flag. Valid themes included `default`, `text`, `slider`, `sliderspotify`, `artleft`, `artright`, `sliderartleft`, and `sliderartright`. The HTML, CSS, and Javascript source for each of these themes is included in the `examples/themes` directory. To use a customized theme, use the `--theme.dir` flag to specify the root of the custom theme directory.

For example, the following command will instruct djctl to serve a custom theme located in the `themes/CoolTheme/` directory directly below the current working directory.

```sh
$ ./djctl start --theme.dir=./themes/CoolTheme/
```

By default, djctl will use the `default` internal theme (`--theme.internal=default` flag). Please note that the presence of `--theme.dir` overrides the `--theme.internal` flag.

#### Placeholders

The `--placeholder.art.internal` flag allows selection of a "placeholder" track art image to use when art metadata is unavailable. Valid placeholder art names include `pixel` and `note`. The `pixel` image is a 1 pixel transparent PNG while the `note` image is a music note.

#### Webhook

djctl can be configured to send a webhook during track transition events. The following example will configure djctl to send webhooks to `http://127.0.0.1:9091/webhook/` after each track transition event:

```sh
$ ./djctl start --webhook.url="http://127.0.0.1:9091/webhook/"
```

Please see the [Webhook API documentation](/docs/reference/webhook-api/) for more information.

### spotify

Spotify-related commands to setup the djctl Spotify source. Please see the [Spotify Quickstart](/docs/quickstart/spotify/) for detailed setup instructions.

{{< callout note >}}
Spotify Premium is required to use the Spotify source.
{{< /callout >}}

Example usge:


```sh
$ ./djctl spotify setup --client-id=<CLIENT_ID> --client-secret=<CLIENT_SECRET>
```

Example output:

```sh
time=2026-03-01T16:47:50.311-08:00 level=INFO source=/Users/mhite/repo/denonctl/cmd/djctl/command/spotify.go:98 msg="Starting Spotify setup"

Open the following URL in your browser to authorize djctl:

  https://accounts.spotify.com/authorize?client_id=<redacted>&redirect_uri=http%3A%2F%2F127.0.0.1%3A8888%2Fcallback&response_type=code&scope=user-read-currently-playing

Waiting for callback on http://127.0.0.1:8888/callback ...

time=2026-03-01T16:51:00.600-08:00 level=INFO source=/Users/mhite/repo/denonctl/cmd/djctl/command/spotify.go:218 msg="Received authorization code, exchanging for tokens..."
time=2026-03-01T16:51:00.834-08:00 level=INFO source=/Users/mhite/repo/denonctl/cmd/djctl/command/spotify.go:225 msg="Received refresh token"
time=2026-03-01T16:51:00.836-08:00 level=INFO source=/Users/mhite/repo/denonctl/cmd/djctl/command/spotify.go:231 msg="Saved token to disk" path=.spotify-token.json

Use --spotifysource.refresh="<REDACTED>" with "djctl start" to use the Spotify source.
```

{{< callout warn >}}
Treat the Spotify secret and refresh token as sensitive credentials. Never share them with anyone.
{{< /callout >}}

### twitch

Twitch-related commands to setup the djctl Twitch extension. Please see the [Twitch Quickstart](/docs/quickstart/twitch/) for detailed setup instructions.

{{< callout note >}}
This is an experimental feature available to licensed users only.
{{< /callout >}}

Example usage:

```sh
$ ./djctl twitch setup
```

When invoking the `twitch setup` command, a Twitch OAuth token is obtained and subsequently exchanged for a djctl Twitch extension API key.

Example:

```sh
 ./djctl twitch setup
time="2024-12-08T10:47:42-08:00" level=info msg="Step 1: Acquire Twitch OAuth token..." _source="cmd/twitch.go:416"
time="2024-12-08T10:47:43-08:00" level=info msg="Please complete device code flow" _source="cmd/twitch.go:216" user_code=SHCLNHHQ verification_uri="https://www.twitch.tv/activate?device-code=<REDACTED>"

Please visit: https://www.twitch.tv/activate?device-code=<REDACTED>
And enter code: <REDACTED>

time="2024-12-08T10:47:53-08:00" level=info msg="token retrieved" _source="cmd/twitch.go:421"
time="2024-12-08T10:47:53-08:00" level=info msg="Saved token to disk" _source="cmd/twitch.go:426" path=.twitch-token.json
time="2024-12-08T10:47:53-08:00" level=info msg="Step 2: Exchange Twitch token for djctl extension API key..." _source="cmd/twitch.go:429"
time="2024-12-08T10:47:53-08:00" level=info msg="API key retrieved" _source="cmd/twitch.go:434" apiKey="<REDACTED>"
time="2024-12-08T10:47:53-08:00" level=info msg="API key saved to file" _source="cmd/twitch.go:439" apiKeyFile=.twitch-extension-api-key.json

Use --twitch.api_key="<REDACTED>" with "djctl start" to send track updates to the Twitch djctl extension
```
{{< callout warn >}}
Treat the Twitch API key as a sensitive credential. Never share it with anyone.
{{< /callout >}}

### version

Shows djctl build and version information.

Example output:

```sh
WARN[0000] No configuration file found
Build info: {0.5.0 3272ba63da0ea201a1ce10477752894d5360bed0 2022-02-18T04:14:43Z goreleaser}
```

## Global flags

All commands provide a common set of global flags as described in the following sections.

### Logging

djctl output is entirely composed of diagnostic log messages. You can control the verbosity of these log messages using the `--log.level` flag. The following log levels are available:

- debug
- info
- warn
- error

By default, djctl runs with a logging verbosity of `info`.

Log messages can be output in either plain text format or in a structured JSON format.

The default text log event is semi-structured into key/value pairs. For example:

```sh
time="2022-02-19T15:05:40-08:00" level=info msg="Requesting device service capabilities" deviceIP=192.168.68.198
time="2022-02-19T15:05:40-08:00" level=info msg="Service discovered" deviceIP=192.168.68.198 serviceName=StateMap servicePort=35349
```

With the `--log.format=json` flag, log messages can be emitted in JSON format. For example:

```sh
{"deviceIP":"192.168.68.198","level":"info","msg":"Requesting device service capabilities","time":"2022-02-19T15:06:41-08:00"}
{"deviceIP":"192.168.68.198","level":"info","msg":"Service discovered","serviceName":"StateMap","servicePort":35349,"time":"2022-02-19T15:06:41-08:00"}
```

For diagnostic purposes, you can also include the source code filename and line number information in the logs. This is controlled through the use of the `--log.line` flag which by default is not enabled. Notice the `_source` field the following example:

```sh
time="2022-02-19T15:09:33-08:00" level=info msg="Requesting device service capabilities" _source="start/start.go:387" deviceIP=192.168.68.198
time="2022-02-19T15:09:34-08:00" level=info msg="Service discovered" _source="start/start.go:401" deviceIP=192.168.68.198 serviceName=FileTransfer servicePort=38769
```

Finally, to output logs to a file, use the `--log-file` flag to specify a file path. When this flag is used, no output will appear on the terminal screen.

### Configuration file

djctl supports the following configuration file formats:

|Format|Extension(s)|
|-|-|
|JSON|json|
|TOML|toml|
|YAML|yaml, yml|
|HCL|hcl, tfvars|
|Java config properties|properties, props, prop|
|envfile|dotenv, env|
|INI|ini|
{.table-striped}

Please see the example YAML file in the djctl `examples/` directory. When crafting a configuration file, the file format will follow the same hierarchy used in the CLI command flags. For example, `--log.file=logs.txt` would be expressed as the following YAML:

```yaml
---
log:
  file: logs.txt
```

To instruct djctl to use a configuration file, use the `--conf` flag to specify its location. The default behavior of djctl is to search the current working directory for a file named `conf` with an extension of the aforementioned configuration file formats. For example, djctl will automatically search for a file named `conf.yaml`.

{{< callout note >}}
Flags passed via the CLI will override flags found in the configuration file.
{{< /callout >}}

### License

To use your personal license, please launch djctl with the `--license.key` command-line parameter followed by the license key text enclosed in double quotes. For example:

```sh
$ ./djctl start --license.key="ASBBDE...<redacted>"
```

Please see the [configuration file](/docs/reference/cli/#configuration-file) section for information on how to create a configuration file where you can permanently place your license key.