Skip to content

Commit

Permalink
Add a visualization for artists
Browse files Browse the repository at this point in the history
  • Loading branch information
2e3s committed Apr 7, 2024
1 parent bebe45d commit a3400d4
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 3 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ jobs:
run: zip "aw-watcher-media-player-linux.zip" aw-watcher-media-player
working-directory: target/${{ env.TARGET }}/release

- name: Add visualization
run: zip -r "target/${{ env.TARGET }}/release/aw-watcher-media-player-linux.zip" visualization

- name: Upload to release
uses: svenstaro/upload-release-action@v2
with:
Expand Down
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,9 @@ features = [

[package.metadata.deb]
extended-description = "ActivityWatch must be available by the given address for this watcher to work."

assets = [
["target/release/aw-watcher-media-player", "usr/bin/", "755"],
["README.md", "usr/share/doc/aw-watcher-media-player/README", "644"],
["visualization/index.html", "usr/share/aw-watcher-media-player/visualization/index.html", "644"],
]
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ Default Windows player
## Installation

- **Linux**:
- Run `sudo unzip aw-watcher-media-player-linux.zip -d /usr/local/bin` in the console to allow ActivityWatch to detect its presence.
- Run `sudo unzip -j aw-watcher-media-player-linux.zip aw-watcher-media-player-linux -d /usr/local/bin` in the console to allow ActivityWatch to detect its presence.
- Optionally, to use visualizations, run `sudo unzip -d /usr/local/share/aw-watcher-media-player/visualization aw-watcher-media-player-linux.zip 'visualization/*'`.
- Or install the attached _.deb_ file.

**Windows**:
Expand All @@ -82,6 +83,27 @@ Use `-vv` to see what's reported.

Note that normally browsers report the currently playing track to the system even in a private mode.

## Custom Visualization

![custom_visualization](images/aw-vizualization-example.png)

This watcher has a visualization which attempts to do its best to display the sorted list of artists with the overall play time for each artist.
Note that ActiveWatch UI gives no abilities for the widget to control its sizing, so it may appear smaller than builtin visualizations.

1. Add the following section to your `aw-server.toml` file in [config directory](https://docs.activitywatch.net/en/latest/directories.html#config):
```toml
[server.custom_static]
aw-watcher-media-player = "/path/to/aw-watcher-media-player/visualization"
# aw-watcher-media-player = "C:\Users\<USER>\AppData\Local\aw-watcher-media-player\visualization"
```
2. Restart ActivityWatch
3. Add custom visualizations from the Activity Watch GUI: `Activity > Edit View > Add Visualization > Custom Visualization`
4. Enter `aw-watcher-media-player` for the watcher name.

The visualization is not customizable from ActivityWatch UI. In order to change, the output, open "index.html":
- Find `getAggregation` function and change `event.data.artist` to `event.data.player` to aggregate by players.
- Change `MAX_AGGREGATIONS` to determine the maximum number of entries (default is 50).

## Build

`cargo build --release` on any platform. See [_release.yml_](https://github.com/2e3s/aw-watcher-media-player/blob/main/.github/workflows/release.yml) for details.
Binary file added images/aw-vizualization-example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
127 changes: 127 additions & 0 deletions visualization/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>aw-watcher-media-player</title>
</head>

<body>
<div id="summary">Loading...</div>
</body>

<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
<script>
// A small hack to make the aw-client work in the browser without webpack
const exports = {};
function require(name) {
if (name === 'axios') {
return axios;
}
throw new Error(`Cannot find module '${name}'`);
}
</script>
<script src="https://cdn.jsdelivr.net/npm/aw-client@0.3.4/out/aw-client.min.js"></script>
<script defer>
const urlParams = new URLSearchParams(window.location.search);
const start = urlParams.get('start');
const end = urlParams.get('end');
const hostname = urlParams.get('hostname');

const client = new AWClient('aw-watcher-media-player', { baseURL: window.location.origin });

function getAggregation(event) {
return event.data.artist;
}
const MAX_AGGREGATIONS = 50;

function formatDuration(duration) {
const hours = Math.floor(duration / 3600);
const remainingSeconds = duration % 3600;
const minutes = Math.floor(remainingSeconds / 60);
const seconds = remainingSeconds % 60;

let formattedTime = '';
if (hours > 0) {
formattedTime += `${hours}h `;
}
if (minutes > 0) {
formattedTime += `${minutes}m `;
}
formattedTime += `${seconds}s`;

return formattedTime;
}

function displayAggregatedDurationChart(aggregatedDurations) {
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");

svg.setAttribute("width", "100%");
svg.setAttribute("height", aggregatedDurations.length * 51);

let y = 0;

const maxDuration = Math.max(...aggregatedDurations.map(({ duration }) => duration));

aggregatedDurations.forEach(({ aggregation, duration }, index) => {
const rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.setAttribute("x", "0");
rect.setAttribute("y", y);
rect.setAttribute("rx", "5");
rect.setAttribute("ry", "5");

// Set the width of the duration rectangle based on the maximum duration
rect.setAttribute("width", `${(duration / maxDuration) * 100}%`);

rect.setAttribute("height", "46");
rect.style.fill = index % 2 === 0 ? 'rgb(204, 204, 204)' : 'rgb(0, 255, 0)';
svg.appendChild(rect);

const textName = document.createElementNS("http://www.w3.org/2000/svg", "text");
textName.setAttribute("x", "5");
textName.setAttribute("y", y + 19.6);
textName.setAttribute("font-family", "sans-serif");
textName.setAttribute("font-size", "14px");
textName.setAttribute("fill", "#333");
textName.textContent = aggregation;
svg.appendChild(textName);

const textDuration = document.createElementNS("http://www.w3.org/2000/svg", "text");
textDuration.setAttribute("x", "5");
textDuration.setAttribute("y", y + 36.4);
textDuration.setAttribute("font-family", "sans-serif");
textDuration.setAttribute("font-size", "11px");
textDuration.setAttribute("fill", "#444");
textDuration.textContent = formatDuration(duration);
svg.appendChild(textDuration);

y += 51;
});

document.getElementById('summary').innerHTML = '';
document.getElementById('summary').appendChild(svg);
}

client.query([`${start}/${end}`], [`RETURN = limit_events(query_bucket("aw-watcher-media-player_${hostname}"), 10);`])
.then((awData) => {
const aggregatedDurationObject = awData[0]
.reduce((acc, event) => {
const aggregation = getAggregation(event);
const duration = event.duration;
acc[aggregation] = (acc[aggregation] || 0) + duration;

return acc;
}, {})
const aggregatedDurationArray = Object.entries(aggregatedDurationObject)
.sort((a, b) => b[1] - a[1])
.map(([aggregation, duration]) => ({ aggregation, duration: Math.round(duration) }));

aggregatedDurationArray.splice(MAX_AGGREGATIONS);

displayAggregatedDurationChart(aggregatedDurationArray);
})
.catch((error) => {
console.error('Error fetching data:', error);
});
</script>

</html>
6 changes: 4 additions & 2 deletions windows.nsi
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ Page instfiles

Section "Install"
SetOutPath $INSTDIR

File "target\x86_64-pc-windows-msvc\release\aw-watcher-media-player.exe"

SetOutPath $INSTDIR\visualization
File /r "visualization\*.*"

EnVar::AddValue "PATH" "$INSTDIR"
SectionEnd

Section "Uninstall"
Delete "$INSTDIR\aw-watcher-media-player.exe"

RMDir /r "$INSTDIR\visualization"
RMDir "$INSTDIR"

EnVar::DeleteValue "PATH" "$INSTDIR"
Expand Down

0 comments on commit a3400d4

Please sign in to comment.