{"id":13405423,"url":"https://github.com/ben-xo/sslscrobbler","last_synced_at":"2025-03-14T09:32:58.974Z","repository":{"id":2158918,"uuid":"615650","full_name":"ben-xo/sslscrobbler","owner":"ben-xo","description":"Serato ScratchLive! / Serato DJ Scrobbler and Twitter bot","archived":false,"fork":false,"pushed_at":"2023-12-13T00:34:36.000Z","size":1489,"stargazers_count":100,"open_issues_count":7,"forks_count":16,"subscribers_count":11,"default_branch":"master","last_synced_at":"2024-07-31T19:45:57.666Z","etag":null,"topics":["php","scrobbler","scrobbling","serato","seratodj","seratodjpro","twitter","twitter-bot"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ben-xo.png","metadata":{"files":{"readme":"README.md","changelog":"historyreader-mac","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null},"funding":{"patreon":"benxo","custom":["https://www.paypal.me/benxo"]}},"created_at":"2010-04-17T22:48:25.000Z","updated_at":"2024-07-30T11:59:41.000Z","dependencies_parsed_at":"2023-12-13T01:37:54.212Z","dependency_job_id":"3d48c362-7c39-4041-b97b-ce8843439c87","html_url":"https://github.com/ben-xo/sslscrobbler","commit_stats":null,"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ben-xo%2Fsslscrobbler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ben-xo%2Fsslscrobbler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ben-xo%2Fsslscrobbler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ben-xo%2Fsslscrobbler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ben-xo","download_url":"https://codeload.github.com/ben-xo/sslscrobbler/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221454096,"owners_count":16824603,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["php","scrobbler","scrobbling","serato","seratodj","seratodjpro","twitter","twitter-bot"],"created_at":"2024-07-30T19:02:01.697Z","updated_at":"2024-10-25T19:32:19.061Z","avatar_url":"https://github.com/ben-xo.png","language":"PHP","funding_links":["https://patreon.com/benxo","https://www.paypal.me/benxo"],"categories":["PHP"],"sub_categories":[],"readme":"# SSLScrobbler v0.30\r\n\r\n[![Testing sslscrobbler](https://github.com/ben-xo/sslscrobbler/actions/workflows/testing.yml/badge.svg)](https://github.com/ben-xo/sslscrobbler/actions/workflows/testing.yml)\r\n\r\nSSLScrobbler is a Scrobbler for Serato DJ and Serato ScratchLive (http://www.serato.com/) \r\nwritten in PHP. See https://www.last.fm/about/trackmymusic for an explanation \r\nof Scrobbling.\r\n\r\nSSLScrobbler is designed to update Last.fm, Twitter and/or Discord when a track\r\nis playing or played. \r\n\r\nIt is so named because Serato DJ used to be called Serato Scratch Live (SSL)\r\nwhen I started this project.\r\n\r\nSSLScrobbler can easily be customed to, for example, show what's currently\r\nplaying on a projector, or send information to OBS (Open Broadcast Studio), or\r\nused to control other actions based on track listing.\r\n\r\nSerato DJ itself logs plays to a binary history file during playback. This app\r\nreads the binary file and models what's going on.\r\n\r\nThe app works on OS X (Mac) and Windows.\r\n\r\n\r\n# 0. CONTENTS\r\n\r\n1. OPERATING SSLSCROBBLER\r\n  * Installation\r\n  * Getting Started\r\n  * Quick HOWTO\r\n  * Options\r\n2. HOW IT WORKS\r\n3. ADVANCED USE\r\n4. TROUBLESHOOTING\r\n5. FOR DEVELOPERS\r\n  * Plugins\r\n  * Unit Tests\r\n  * Architecture\r\n6. THANKS \u0026 SHOUTS\r\n7. FAQ\r\n8. CREDITS \u0026 LICENSE\r\n\r\n\r\n# 1. OPERATING SSLSCROBBLER\r\n\r\nYou should start SSLScrobbler before starting Serato DJ!, and then close it \r\ndown after closing down Serato DJ. If you have no idea how to start it, See \r\n1.2 for the 'Getting Started' guide. \r\n\r\nTo close SSL Scrobbler, press Ctrl-C.\r\n\r\nSSLScrobbler will read the current 'session' file from the Serato DJ history \r\nfolder. It will follow from session to session, but use the `-i` option if you\r\nalready have a session open.\r\n\r\n\r\n## 1.1 Installation\r\n\r\n\r\n### 1.1.1 macOS\r\n    \r\n  There is an app you can download from https://github.com/ben-xo/sslscrobbler/releases\r\n\r\n  SSLScrobbler needs no particular installation, although if it's not already, \r\n  you might like to install `terminal-notifier` (\"`brew install terminal-notifier`\" -\r\n  If you don't have the `brew` command, install Mac Homebrew from https://brew.sh/ )\r\n\r\n  SSLScrobbler can also be started from Terminal, and has more options this way.\r\n\r\n  To start from Terminal you will need PHP installed (recommended version 7.4).\r\n  You can install PHP through Mac Homebrew https://brew.sh/. Once installed,\r\n  \r\n        brew install php@7.4\r\n\r\n  See \"Getting Started\" for more.\r\n\r\n\r\n### 1.1.2 Windows\r\n  \r\n  I haven't tested this part in a while - try installing PHP from\r\n  http://www.anindya.com/ or following the instructions on https://www.php.net/\r\n   \r\n  You should install PHP 8 and Growl. You must reboot after installing these, \r\n  even if it doesn't ask!\r\n  \r\n  If you're on Windows XP, 32-bit Vista or 32-bit Windows 7, you can download \r\n  and install PHP from http://windows.php.net/download/\r\n  (You probably want the 'Installer' nearest the top of the page, unless you\r\n  know better.)\r\n   \r\n  I strongly suggest you make the following change to your `php.ini` file (which \r\n  can usually be found in `C:\\Program Files\\PHP`). Open the file in Notepad, and \r\n  then find the line which says...\r\n  \r\n        display_errors = \r\n    \r\n  ...and change it to `On` if it is `Off`.  \r\n  \r\n  SSLScrobbler is best started from a DOS box / Command prompt (see below)\r\n\r\n\r\n## 1.2 Getting Started\r\n\r\n\r\nSSLScrobbler is designed to be run from the command prompt / terminal, but on \r\nmacOS there is also a GUI to help you get started.\r\n\r\n\r\n### 1.2.1 TO START\r\n\r\nI use this app through Terminal on macOS. But, there is a macOS GUI version too.\r\nOn Windows you must use Command Prompt. There is a guided-prompt setup mode.\r\nRead on.\r\n\r\n#### 1.2.1.1 macOS (super-easy method):\r\n \r\nThe simplest way to get started is to use the macOS app. \r\n * Download the macOS.zip file from https://github.com/ben-xo/sslscrobbler/releases\r\n * unzip it to get `SSL Scrobbler.app` (with a nice vinyl record icon)\r\n * Option-click it and choose \"Open\", then confirm you are sure that it's okay\r\n * The app will ask you for permission to use `SystemUIServer`. Say yes. This is \r\n   so we can pop up questions.\r\n * It will then pop up some questions. Just follow the prompts.\r\n * Then start Serato DJ and watch what happens!\r\n\r\n\r\n#### 1.2.1.2 macOS (traditional method / more options):\r\n\r\nIt is more flexible when used from Terminal.\r\n\r\n * Open Terminal (you can open it quickly from Spotlight)\r\n * Drag the file historyreader.php into the Terminal window, and hit enter. It \r\n   should say something like: \r\n    \r\n        $ /Users/ben/Downloads/sslscrobbler/historyreader.php \r\n    \r\n * For help and information on options, type `--help` before hitting enter. e.g: \r\n\r\n        $ ./historyreader.php --help\r\n\r\n * For the guided setup mode, try `--prompt` .\r\n\r\n\r\n#### 1.2.1.3 Windows:\r\n\r\n There's no GUI version for Windows yet. Sorry.\r\n \r\n * Open a Command Prompt. You can do this by clicking 'Start' -\u003e Run -\u003e typing \"`cmd`\" \r\n   and pressing enter. \r\n * Type `php` and then drag the file `historyreader.php` into the command prompt, and hit \r\n   enter. It should say something like:\r\n    \r\n        C:\\\u003e php \"C:\\Documents and Settings\\ben\\Desktop\\historyreader.php\"\r\n    \r\n * For help and information on options, type `--help` before hitting enter. e.g.:\r\n \r\n        C:\\\u003e php \"C:\\Documents and Settings\\ben\\Desktop\\historyreader.php\" --help\r\n \r\n * For guided setup mopde, try `--prompt` .\r\n\r\n\r\n### 1.2.2 TO QUIT\r\n\r\nTo quit SSL Scrobbler, click on its window and press `Ctrl`+`C`.\r\n(or in the macOS app, press the quit button in the bottom right)\r\n\r\n\r\n## 1.3 Quick HOWTO\r\n\r\n\r\nTO SCROBBLE AS YOU PLAY:\r\n\r\n    php historyreader.php -L lastfmusername\r\n\r\n\r\nTO SCROBBLE THE PREVIOUS SET (e.g. from your gig last night):\r\n\r\n    php historyreader.php -L lastfmusername --post-process\r\n\r\n\r\nTO SCROBBLE SEVERAL PEOPLE IN THE ROOM:\r\n\r\n    php historyreader.php -L lastfmusername -L lastfmusername2 -L lastfmusername3\r\n\r\n\r\nTO TWEET AS YOU PLAY\r\n\r\n    php historyreader.php -T twitterusername\r\n\r\n\r\nTO MESSAGE DISCORD AS YOU PLAY\r\n\r\n    php historyreader.php --discord webhook-name\r\n\r\n\r\nMAKE TRACK DATA AVAILABLE FOR OBS (OR WHATEVER)\r\n\r\n    php historyreader.php -J 8080\r\n\r\n\r\nMAKE TRACK DATA FOR BUTT (OR WHATEVER)\r\n\r\n    php historyreader.php -ln nowplaying.txt\r\n\r\n\r\n## 1.4 Options\r\n\r\nAdd the following options to the command when running from Terminal / DOS to\r\ncontrol the behaviour.\r\n\r\nIf you supply no options, SSLScrobbler will show you that it's working, but the\r\ninformation won't be sent anywhere or available to use except by reading the\r\noutput; so you will want to study the options (especially the options from\r\n`--plugin-help`)\r\n\r\n### 1.4.1 Basic options\r\n\r\n\r\n`-h` or `--help`\r\nA reminder of this information.\r\n\r\n`--prompt`\r\nGuided setup mode.\r\n\r\nYou will be asked a series of yes or no questions. (Not all options are available\r\nin this mode).\r\n\r\n`-l` or `--log-file \u003cfile\u003e`:\r\nWrite the output to a file. (If this option is omitted, output goes to the \r\nscreen)\r\n\r\n`-i` or `--immediate`\r\nDo not wait for the next history file to be created, but use the most recent \r\none.\r\n   \r\nYou must use this option if you started SSLScrobbler mid-way through a \r\nsession, or if you had to restart SSLScrobbler for some reason.\r\n \r\nThis option is ignored if you specify the full path to a specific history \r\nfile.\r\n   \r\n`-v` or `--verbosity \u003c0-9\u003e`:\r\nIncrease the amount of information shown in the console. If you really want to\r\nsee a lot about what's going on, try `-v 9` . \r\n \r\nYou should try `-v 9` and save the output if you are having problems, before \r\nreporting a bug to me, or contacting me for advice...\r\n \r\n\r\n`-p` or `--post-process`\r\nImmediately processes everything in the last history file. Ideal for\r\nscrobbling that set you played last night.\r\n \r\n**Now Playing text file options**\r\nAll of these options output the current playing track to a file, but in different\r\nformats.\r\n\r\n`-ln` or `--log-track \u003cfile\u003e`:\r\nlog the current playing track to a file (e.g. for streaming)\r\n\r\nThe file will contain the text `Artist Name - Song Title` when a song is playing,\r\nand the file will be empty the rest of the time.\r\n\r\n`-ls` or `--log-serialized \u003cfile\u003e`:\r\nlog the current playing track to a file in PHP serialized form. This contains more\r\ninfo, but is not human readable - it's useful if you want to build other PHP\r\nscripts for a web server.\r\n\r\n`-lt` or `--log-tostring \u003cfile\u003e`: log the current playing track to a file in a\r\nfuller representation, like what comes out in the console log, e.g.:\r\n\r\n    PLAYED:1 - ADDED:1 - DECK:1 - Artist - Title - 0:0\r\n\r\nMost people want `-ln`\r\n\r\n\r\n**Last.fm options**:\r\n`-L` or `--lastfm \u003cusername\u003e`: \r\nScrobble / send 'Now Playing' to Last.fm for user \u003cusername\u003e. \r\n\r\nThe first time you specify this, it will ask you to authorize the app to your \r\nLast.fm account. The authorization information is stored in a file called \r\n`lastfm-\u003cusername\u003e.txt`\r\n\r\nNOTE: you can include `-L` multiple times and scrobble to multiple accounts.\r\n  \r\n**Twitter options**:\r\n`-T` or `--twitter \u003csession\u003e`:\r\nPost tracklists to Twitter. It will tweet once for every 'Now Playing'. \r\n\r\nThe first time you specify this option, it will ask you to authorize the app \r\nto your Twitter account. The authorization information is stored in a file \r\ncalled `twitter-\u003csession\u003e.txt`\r\n\r\nNOTE: you can include `-T` multiple times and tweet to multiple accounts.\r\n\r\n**Discord options**:\r\n`--discord \u003csession\u003e`:\r\nPost tracklists to Discord. It will send a message once for every 'Now Playing'. \r\n\r\nThe first time you specify this option, it will ask you to paste a URL for a webhook\r\nwhich can be provided by your guild admin for a channel on your server. The \r\nauthorization information is stored in a file called `discord-\u003csession\u003e.txt`\r\n\r\nNOTE: you can include `--discord` multiple times and message multiple channels.\r\n  \r\n**DB options**:\r\n`-D` or `--db \u003ckey\u003e`:\r\nPut the now playing track into a database row. It will issue one SQL \r\nstatement for every 'Now Playing'.\r\n\r\nExactly what SQL is run, and where it is sent, is configued in `config.php`.\r\n  \r\n**IRCCat options**:\r\n`-I` or `--irccat host:port#channel`\r\nPut the now playing track into an IRC channel using IRCCat. (IRCCat sold\r\nseparately - https://github.com/RJ/irccat).   \r\n\r\n\r\n**JSON Server options**\r\n`-J` or `--json port`\r\nMakes the current playing track info available at `http://\u003cyour ip\u003e:\u003cport\u003e/nowplaying.json`.\r\nAlso, makes it available in a way which can be styled using CSS (which e.g. for OBS) at \r\n`http://\u003cyour ip\u003e:\u003cport\u003e/nowplaying.html`\r\n\r\n\r\n# 2. HOW IT WORKS\r\n\r\n\r\nSSLScrobbler monitors the current Serato DJ history file. The history file is\r\na binary file containing information about all the tracks in the session. \r\nSerato DJ updates this file every time you add a track to a deck or eject a \r\ntrack from a deck (and in a few other situations). The history file actually \r\ncontains a lot of information - everything you see in the history pane, and\r\nthen some. ScratchLive never removes or rewrites information in this file while\r\nyou're performing, so it may append several chunks of information referring to \r\nthe same track. (Later, when you shut Serato DJ down, it compacts the file to \r\nremove duplicate information).\r\n\r\nHowever, SSLScrobbler does not have access to the actual play time or play \r\nposition of the songs, so it has to guess this. See the FAQ section for more\r\ninfo about the heuristic used for this \"best guess\".\r\n\r\n\r\n# 3. ADVANCED USE\r\n\r\n\r\n* If you want to enable or disable plugins, or change API keys, or other\r\n  advanced \"configuration\", copy `config.php-default` to `config.php` and edit.\r\n  You can also change e.g. the Twitter template here.\r\n\r\n* If you're interested in exploring the Serato DJ binary file format, check \r\n  out the `--dump` option. You can even use this to dump non-history files\r\n  (such as the file `database v2`). \r\n \r\n \r\n# 4. TROUBLESHOOTING\r\n\r\n\r\n* SSLScrobbler looks for history files in the default locations, which are:\r\n  \r\n  Mac:\r\n  * `$HOME/Music/_Serato_/History/Sessions`\r\n  \r\n    (`$HOME` is usually e.g. `/Users/\u003cusername\u003e`)\r\n  \r\n  Windows Vista / Windows 7 / Windows 10:\r\n  * `%USERPROFILE%\\Music\\_Serato_\\History\\Sessions`\r\n\r\n    (`%USERPROFILE%` is usually e.g. `C:\\Users\\\u003cusername`)\r\n    \r\n  Windows XP:\r\n  * `%USERPROFILE%\\My Documents\\My Music\\_Serato_\\History\\Sessions`\r\n  \r\n    (`%USERPROFILE%` is usually e.g. `C:\\Documents and Settings\\\u003cusername\u003e`)\r\n  \r\n* make sure `display_errors = On` in your `php.ini` if you want more useful help, \r\n  and before reporting bugs.\r\n  \r\n* If the internet is down or drops out, posting to any of the services (e.g.\r\n  Last FM, Twitter, Discord, etc) may make the whole app freeze until the\r\n  attempt times out. This means that updates to Now Playing will not appear \r\n  during this period, and the message may not appear on the service (although \r\n  in the case of the Last FM plugin, no scrobbles will be lost, they'll be\r\n  submitted later).\r\n  \r\n* If you find Scrobbling, Tweeting etc to be particularly slowing down the app \r\n  (that is causing delays when Now Playing is not updated), try installing the \r\n  `PCNTL` extension to PHP. (Without the PCNTL extension, the app will be \r\n  single-threaded). However, I've found it doesn't really make much difference.\r\n  \r\n \r\n# 5. FOR DEVELOPERS\r\n\r\n \r\n## 5.1 Plugins\r\n\r\n\r\nIt's quite easy to write plugins for SSLScrobbler. Examine the examples in the \r\nfolder `SSL/Plugins`.\r\n\r\nAll plugins can be can be activated (or deactivated) by adding them into\r\n`config.php` (with a sensible default set provided in `config.php-default`).\r\nMost of them have options you can set too.\r\n\r\nMost plugins can be configured more than once, for example to tweet to two\r\naccounts.\r\n\r\nAny class which implements `SSLPlugin` can be configured as a plugin,\r\nand can provide zero-or-more Observers for the different events from\r\nthe `getObservers()` method. The Observers are for reacting to the various\r\nsignals which are generated throughout the session.\r\n\r\nThe following observer types are currently provided:\r\n* `TickObserver` - triggered by a timer interrupt (usually every 2 seconds).\r\n* `SSLDiffObserver` - notified of new changes to the Serato DJ history file.\r\n* `TrackChangeObserver` - triggered when a track is loaded or removed from a deck.\r\n* `NowPlayingObserver` - triggered when a track becomes the 'Now Playing' track\r\n* `ScrobbleObserver` - triggered when a track is definitively scrobble-able. \r\n\r\nMany plugins can also be configured through command line options. If a plugin\r\nwhich also implements the interface `CLIPlugin` is configured in `config.php`,\r\nit will present its options in `--plugin-help`. Most of the plugins useful\r\nfor DJing work this way.\r\n\r\n`CLIPlugin`s may also provide prompts for the `--guided` setup mode.\r\n\r\nI choose to write my CLI plugin counterparts as separate plugins in their own\r\nright (plugins for adding more plugins, basically), and you can see that it's\r\nmostly the CLI versions which are in the default config. All you need to do is\r\nimplement the right interfaces, however. You don't have to copy my style\r\nif you don't want to (but it's probably easiest if you do).\r\n\r\n## 5.2 Unit Tests\r\n\r\n\r\nRun with phpunit:\r\n * `phpunit --bootstrap Tests/bootstrap.php Tests`\r\n\r\n## 5.3 Architecture\r\n\r\n\r\n### 5.3.1 Runtime Model\r\n\r\n\r\nWhile running, the `SSLScrobbler` engine is event driven (see 5.1 for the list\r\nof events). Here are the main object collaborations and the ways they \r\ncommunicate. The interactions happen in serial, in the order they are numbered.\r\n\r\nThe following diagram shows how the running app is strung together. \r\n`HistoryReader` sets these objects up in its `monitor()` method, then asks\r\nthe `TickSource` to start ticking. Every tick, the following happens:\r\n\r\n1. The `TickSource` sends ticks (every 2 seconds or so) to \r\n   `SSLHistoryFileMonitor`, which attempts to read from the current\r\n   history file. \r\n2. If there is new info available in the file, the `SSLHistoryFileMonitor`\r\n   sends a diff event (in the form of an `SSLHistoryDiffDom`, which in turn\r\n   contains `SSLTracks`) to the `SSLRealtimeModel`.\r\n3. The `SSLRealtimeModel` models what Serato is doing - i.e. which tracks\r\n   are currently on each deck. It inspects the `SSLHistoryDiffDom`s that it\r\n   receives to work out if a new track has been started, or a track has been\r\n   stopped.\r\n4. If a track has changed (started or stopped), the `SSLRealtimeModel` then\r\n   notifies the `NowPlayingModel`, `ScrobbleModel` and `RealtimeModelPrinter`.\r\n5. The `RealtimeModelPrinter` prints this info to the console.\r\n6. The `NowPlayingModel` takes info on track changes to work out which track\r\n   has been on the deck long enough to be considered \"Now Playing\".\r\n7. The `ScrobbleModel` takes info on track changes to work out which tracks\r\n   can be scrobbled.\r\n8. Whenever the \"Now Playing\" track changes (or track play stops entirely),\r\n   the `NowPlayingModel` sends events - mostly to plugins such as the \r\n   Twitter plugin, Scrobbler, and Growl or Terminal notifier.\r\n9. Likewise, when a track becomes Scrobbleable, the `ScrobbleModel` sends \r\n   events to the Twitter, Scrobble and Growl plugins, etc.\r\n10. The various plugins then do their bits such as posting to Twitter.\r\n\r\nHere's the diagram:\r\n\r\n    +------------+\r\n    | TickSource |  \r\n    +-----+------+\r\n          |\r\n          | A timer event (roughly every 2 seconds)\r\n          |\r\n          |-----------------------+-----------------------------------------+\r\n          v 2                     v 1                                       |\r\n    +-----------------------+ +---------------+                             |\r\n    | SSLHistoryFileMonitor | | PluginManager |                             |\r\n    +-----+-----------------+ +---------------+                             |\r\n          |                                                                 |\r\n          | Diff event (when history file changes) as an                    |\r\n          | \u003cSSLHistoryDiffDom\u003e object (which contains \u003cSSLTrack\u003es)         |\r\n          |                                                                 |\r\n          v 3                                                               |\r\n    +------------------+  6. \u003cTrackChangeEventList\u003e from deck models sent   |\r\n    | SSLRealtimeModel +----------------------------------------------+     |\r\n    +------------------+                                              |     |\r\n          ^                                                           |     |\r\n          | 4. Sent: Diff event (delegated to correct deck model)     |     |\r\n          | 5. Received: \u003cTrackChangeEvent\u003es (start, stop, update)    |     |\r\n          |                                                           |     |\r\n          |---------------+------------ . . . --------------+         |     |\r\n          v               v    decks created as necessary   v         |     |\r\n    +---------------+ +---------------+       +---------------+       |     |\r\n    | DeckModel (0) | | DeckModel (1) | . . . | DeckModel (n) |       |     |\r\n    +---------------+ +---------------+       +---------------+       |     |\r\n                                                                      |     |\r\n                                                                      |     |\r\n       +----+--------------------------+-------------------------+----+     |\r\n       |    | Print track changes      | Decide if a stopped     |       +--+\r\n       |    | to console               | track should scrobble   |       |\r\n       |    v 7                        v 12                      v 8     v 16\r\n       |  +----------------------+   +---------------+    +-----------------+ \r\n       |  | RealtimeModelPrinter |   | ScrobbleModel |    | NowPlayingModel |\r\n       |  +----------------------+   +-+-------------+    +--------------+--+\r\n       |                               |                                 |\r\n       |                               | \"Scrobble\"        \"Now Playing\" |   \r\n       |                               |  event                   event  +--+\r\n       |                               |                                    |\r\n       |                               .                                    .\r\n       |                               . . . . Other Plugins  . . . . . . . .\r\n       |                               .                                    .\r\n       |                               |                                    |\r\n       |                               |                                    |\r\n       |                               | 14  +---------------------+  11,19 |\r\n       |                               +----\u003e| SSLScrobblerAdaptor |\u003c-------+ \r\n       |                               |     +---------------------+        |\r\n       |                               |                                    |\r\n       |                               |     +---------------------+  10,18 |\r\n       |                               |     | SSLTwitterAdaptor   |\u003c-------+\r\n       |                               |     +---------------------+        |\r\n       |                               |                                    |\r\n       |                               | 13  +---------------------+   9,17 |\r\n       |                               +----\u003e| SSLGrowlRenderer    |\u003c-------+\r\n       |                                     +---------------------+\r\n       | Print track changes via Growl         ^ 15\r\n       +---------------------------------------+\r\n   \r\n   \r\nVarious things have been omitted from this diagram, in particular the \r\ndetails of how the `PluginManager` works. The `PluginManager` is capable of\r\nactivating, deactivating and reconfiguring plugins in the event chain\r\nat run time, and is used for configuration on-the-fly. It does this\r\nby inserting a layer between each of the observers which keeps track of the\r\nvarious event observers.\r\n\r\n### 5.3.2 ScratchLive File Format Model\r\n\r\n\r\nScratchLive stores most of its data in a chunked format, where a chunk header \r\nis 8-bytes (4-byte identifier and a 4-byte length) followed by \u003clength\u003e bytes. \r\nChunks themselves can contain other chunks. Within these sub-chunks are \r\nfields, starting with a 4-byte field ID. The meaning of the fields depends on\r\nthe chunk type. Some fields contain fixed-length data, others contain a 4-byte\r\nlength and then that many bytes of variable-length data.\r\n\r\nWhilst exploring the file format, I invented an unpacking language called\r\nXOUP (short for \"XO's UnPacker\"). XOUP is interpretted with XoupInterpreter or\r\ncompiled into Unpacker classes with XoupCompiler (all this happens \r\nautomatically). See the comments in XoupInterpreter for info on XOUP.\r\n\r\nScratchLiveScrobbler, so far, recognises 7 chunk types. Some of these are \r\n\"compound chunks\" (that is, they contain other chunks), and others contain data \r\n(\"struct chunks\"):\r\n\r\nCompound Chunks:\r\n* `OENT` - Session files have these, each containing a single `ADAT` for a track\r\n* `OREN` - Session files have these, each containing a single `UEN` for a deletion\r\n* `OSES` - The session index has these, each with a single `ADAT` for a session\r\n* `OCOL` - The session index file has these each with `UCOK` and `UCOW` sub-chunks.\r\n\r\nStruct Chunks:\r\n* `VRSN`\r\n  - Header chunk, contains a file format string. Occurs in all files\r\n  - Parsed by `SSLVrsnChunk` into an `SSLVersion` object using `SSLVersionVrsn.xoup`\r\n  \r\n* `ADAT` - two of these, `OENT ADAT` and `OSES ADAT`\r\n  - Data chunk, contains fields. Fields meaning file format dependent\r\n  1. `OENT` version, parsed by `SSLAdatChunk` into an `SSLTrack` object \r\n    using `SSLTrackAdat.xoup`\r\n  2. `OSES` version, parsed by `SSLAdatChunk` into an `SSLSession` object \r\n    using `SSLSessionAdat.xoup`\r\n    \r\n* `UENT`\r\n  - Event chunk, seems to contain just an identifier referring to an `OENT ADAT`.\r\n  - Parsed by `SSLUentChunk` into an `SSLTrackDelete` object \r\n    using `SSLTrackDeleteUent.xoup`\r\n  - These occur transiently in session files when an entry is deleted from the\r\n    playlist. Serato DJ seems to resolve these and rewrite the history file\r\n    at shut-down time.\r\n    \r\n* `UCOK` and `UCOW`\r\n  - I believe these represent column ordering and column width in the \r\n    Serato DJ history pane.\r\n  - I have not written parsers for these yet.\r\n\r\nUnknown chunk types are safely ignored (modelled by `SSLUnknownChunk` - in\r\n`--dump` mode, these will provide a pretty hexdump to aid with implementation). \r\n\r\nThe Serato DJ crate file (`database v2`) is also in this format, but I have \r\nnot modelled any of it. Have fun exploring these files using `--dump` :)\r\n\r\n### 5.3.3 Example content\r\n\r\nHere's an example of what `--dump` might output on a history file:\r\n\r\n    CHUNK\u003cvrsn\u003e: \r\n    \tversion =\u003e 1.0/Serato Scratch LIVE Review\r\n\r\n    CHUNK\u003coent\u003e: \r\n    \t\tCHUNK\u003cadat\u003e: \r\n    \t\t\trow =\u003e 3137\r\n    \t\t\tfullpath =\u003e /Users/ben/04 - )E!3( - Bad Company - Grunge 2.mp3\r\n    \t\t\tlocation =\u003e /Users/ben\r\n    \t\t\tfilename =\u003e 04 - )E!3( - Bad Company - Grunge 2.mp3\r\n    \t\t\ttitle =\u003e Grunge 2\r\n    \t\t\tartist =\u003e )E|3( - Bad Company\r\n    \t\t\talbum =\u003e Book Of The Bad (CD2)\r\n    \t\t\tgenre =\u003e Drum \u0026 Bass\r\n    \t\t\tlength =\u003e 06:25.31\r\n    \t\t\tbitrate =\u003e 320.0kbps\r\n    \t\t\tcomments =\u003e Track 4\r\n    \t\t\tlang =\u003e eng\r\n    \t\t\tyear =\u003e 2001\r\n    \t\t\tstarttime =\u003e 1272398586\r\n    \t\t\tendtime =\u003e 1272398677\r\n    \t\t\tdeck =\u003e 2\r\n    \t\t\tplaytime =\u003e 91\r\n    \t\t\tsessionId =\u003e 3135\r\n    \t\t\tplayed =\u003e 0\r\n    \t\t\tadded =\u003e 0\r\n    \t\t\tupdatedAt =\u003e 1272398677\r\n\r\nThe same data, without the `XOUP` parser, would have printed this:\r\n\r\n    CHUNK\u003coent\u003e: \r\n    \t\tCHUNK\u003cadat\u003e: \r\n    \t\t\t0000 0001 0000 0004 0000 0c41 0000 0002 0000 00dc 002f 0055 0073 0065 0072 0073 ...1...4..CA...2...!./.U.s.e.r.s\r\n    \t\t\t002f 0062 0065 006e 002f 0044 006f 0077 006e 006c 006f 0061 0064 0073 002f 0042 ./.b.e.n./.D.o.w.n.l.o.a.d.s./.B\r\n    \t\t\t0043 0020 0052 0065 0063 006f 0072 0064 0069 006e 0067 0073 002f 0042 0043 0052 .C. .R.e.c.o.r.d.i.n.g.s./.B.C.R\r\n    \t\t\t0055 004b 0045 0050 0043 0044 0030 0030 0031 0020 002d 0020 0042 006f 006f 006b .U.K.E.P.C.D.0.0.1. .-. .B.o.o.k\r\n    \t\t\t0020 006f 0066 0020 0054 0068 0065 0020 0042 0061 0064 002f 0043 0044 0032 002f . .o.f. .T.h.e. .B.a.d./.C.D.2./\r\n    \t\t\t0030 0034 0020 002d 0020 0029 0045 0021 0033 0028 0020 002d 0020 0042 0061 0064 .0.4. .-. .).E.!.3.(. .-. .B.a.d\r\n    \t\t\t0020 0043 006f 006d 0070 0061 006e 0079 0020 002d 0020 0047 0072 0075 006e 0067 . .C.o.m.p.a.n.y. .-. .G.r.u.n.g\r\n    \t\t\t0065 0020 0032 002e 006d 0070 0033 0000 0000 0003 0000 008c 002f 0055 0073 0065 .e. .2...m.p.3.....3...!./.U.s.e\r\n    \t\t\t0072 0073 002f 0062 0065 006e 002f 0044 006f 0077 006e 006c 006f 0061 0064 0073 .r.s./.b.e.n./.D.o.w.n.l.o.a.d.s\r\n    \t\t\t002f 0042 0043 0020 0052 0065 0063 006f 0072 0064 0069 006e 0067 0073 002f 0042 ./.B.C. .R.e.c.o.r.d.i.n.g.s./.B\r\n    \t\t\t0043 0052 0055 004b 0045 0050 0043 0044 0030 0030 0031 0020 002d 0020 0042 006f .C.R.U.K.E.P.C.D.0.0.1. .-. .B.o\r\n    \t\t\t006f 006b 0020 006f 0066 0020 0054 0068 0065 0020 0042 0061 0064 002f 0043 0044 .o.k. .o.f. .T.h.e. .B.a.d./.C.D\r\n    \t\t\t0032 0000 0000 0004 0000 0050 0030 0034 0020 002d 0020 0029 0045 0021 0033 0028 .2.....4...P.0.4. .-. .).E.!.3.(\r\n    \t\t\t0020 002d 0020 0042 0061 0064 0020 0043 006f 006d 0070 0061 006e 0079 0020 002d . .-. .B.a.d. .C.o.m.p.a.n.y. .-\r\n    \t\t\t0020 0047 0072 0075 006e 0067 0065 0020 0032 002e 006d 0070 0033 0000 0000 0006 . .G.r.u.n.g.e. .2...m.p.3.....6\r\n    \t\t\t0000 0012 0047 0072 0075 006e 0067 0065 0020 0032 0000 0000 0007 0000 0028 0029 ...I.G.r.u.n.g.e. .2.....7...(.)\r\n    \t\t\t0045 007c 0033 0028 0020 002d 0020 0042 0061 0064 0020 0043 006f 006d 0070 0061 .E.|.3.(. .-. .B.a.d. .C.o.m.p.a\r\n    \t\t\t006e 0079 0000 0000 0008 0000 002c 0042 006f 006f 006b 0020 004f 0066 0020 0054 .n.y.....8...,.B.o.o.k. .O.f. .T\r\n    \t\t\t0068 0065 0020 0042 0061 0064 0020 0028 0043 0044 0032 0029 0000 0000 0009 0000 .h.e. .B.a.d. .(.C.D.2.).....9..\r\n    \t\t\t0018 0044 0072 0075 006d 0020 0026 0020 0042 0061 0073 0073 0000 0000 000a 0000 .O.D.r.u.m. .\u0026. .B.a.s.s.....A..\r\n    \t\t\t0012 0030 0036 003a 0032 0035 002e 0033 0031 0000 0000 000d 0000 0014 0033 0032 .I.0.6.:.2.5...3.1.....D...K.3.2\r\n    \t\t\t0030 002e 0030 006b 0062 0070 0073 0000 0000 0011 0000 0010 0054 0072 0061 0063 .0...0.k.b.p.s.....H...G.T.r.a.c\r\n    \t\t\t006b 0020 0034 0000 0000 0012 0000 0004 656e 6700 0000 0017 0000 000a 0032 0030 .k. .4.....I...4eng....N...A.2.0\r\n    \t\t\t0030 0031 0000 0000 001c 0000 0004 4bd7 42fa 0000 001d 0000 0004 4bd7 4355 0000 .0.1.....S...4K!B!...T...4K!CU..\r\n    \t\t\t001f 0000 0004 0000 0002 0000 0021 0000 0001 0000 0000 2d00 0000 0400 0000 5b00 .V...4...2...!...1....-...4...[.\r\n    \t\t\t0000 3000 0000 0400 000c 3f00 0000 3200 0000 0100 0000 0034 0000 0001 0000 0000 ..0...4..C?...2...1....4...1....\r\n    \t\t\t3500 0000 044b d743 55                                                          5...4K!CU\r\n\r\nAll of the field names in the properly parsed output were worked out with \r\neducated guess-work. (The list of fields shown for the track in the example is\r\nrepresentative, but not all possible fields are saved with each row in the\r\nhistory file. Other possible fields include bpm, album artist, etc). \r\n\r\nHere's how the software side of it is strung together.\r\n\r\nThe `SSLHistoryFileDiffMonitor` is responsible for monitoring the file by continually\r\nreading it to see what's been appended. Once it sees new data, it will parse the data\r\ninto chunks and give those raw chunks to an `SSLHistoryDom` to look after. This first\r\nphase looks like this:\r\n\r\n    SSLHistoryFileDiffMonitor (keeps reading a History file to look for more chunks)\r\n      | read()\r\n      v\r\n      SSLParser \u003cnew SSLHistoryDom $tree\u003e (a parser that reads the file into the chosen DOM)\r\n        | parse(\u003cfilename\u003e)\r\n        | readChunks()\r\n        v\r\n        SSLChunkReader (reads all chunks from a file and parses them)\r\n          | $dom-\u003eaddChunks( getChunks() ) \r\n          v\r\n          SSLChunkParser (reads a specific chunk from the file and constructs concrete chunks)\r\n            | readChunk() // loop\r\n            | -\u003eparseFromFile()\r\n            v\r\n            SSLChunkFactory (creates concrete chunk instances from the chunk headers)\r\n              | newChunk()\r\n              v\r\n              SSLChunk \u003cSSLVrsnChunk / SSLOentChunk / SSLAdatChunk / ... etc\u003e\r\n              |  | __construct()\r\n              |  v\r\n              |  SSLCompoundChunk (such as OENT, breaks down into more chunks)\r\n              |   | \u003cthe entire SSLChunkParser stack recursively from SSLChunkParser down\u003e\r\n              |   \r\n              v\r\n              SSLStructChunk (such as ADAT. contains actual data).\r\n          \r\nAt this point, the $tree object contains all of the raw data separated into \r\nchunks which know what they are and how to extract data from themselves.\r\n\r\nThe full data is not actually parsed and extracted from the $tree object until someone \r\ncalls `$tree-\u003egetTracks()`. However, as the `SSLHistoryFileDiffMonitor` generates diffs,\r\nthe next thing it does is to get the tracks from this DOM and the previous DOM and\r\ndiff them. The data extraction part looks like this:\r\n\r\n    SSLHistoryFileDiffMonitor\r\n     | getNewOrUpdatedTracksSince()\r\n     v \r\n     SSLHistoryDom\r\n       | getTracks() // gets all data but filters it just to SSLTracks\r\n       | -\u003egetData() \r\n       v \r\n       SSLChunk \u003cany of the various chunk types, e.g. OENT\u003e\r\n         | getDataInto(new SSLStruct) // the HistoryDom knows the appropriate struct type\r\n         |                            // e.g. it knows that an OENT ADAT needs \u003cSSLTrack\u003es\r\n         v\r\n         SSLStruct\r\n          | getUnpacker() // the struct knows its own unpacker (a XOUP file)\r\n          v\r\n          XoupLoader\r\n            |\r\n            v\r\n            XoupCompiler\r\n              |\r\n              v\r\n              Unpacker (a compiled subclass of Unpacker such as XOUPSSLTrackAdatUnpacker)\r\n          \r\nAt this point, we have concrete `SSLTrack` objects.\r\n     \r\n\r\n# 6. THANKS \u0026 SHOUTS\r\n\r\n\r\nThanks:\r\n* Jesse Ward (jw76), for beta testing and bug reports\r\n* Jason Salaz (VxJasonxV) for the Nicecast plugin\r\n* Dan Etherington (baseonmars), Zac Stevens (zts) and Attila Gyorffy (djliquiduk)\r\n  for beta testing, feedback and moral support\r\n* DJ NightLife for his support of the project!\r\n* Brian Tiger Chow for bug reports and patches\r\n* Nick Masi (N-Masi) for code contributions to logging\r\n\r\nShouts:\r\n* Last.fm for letting me use the back room of the office to broadcast loud radio\r\n  shows. Oh, and for employing me. Thanks!\r\n* Donnovan, Louis and Daniel at Bassdrive (bassdrive.com)\r\n* Mike Louth at Digitally Imported (di.fm)\r\n\r\n\r\n# 7. FAQ\r\n\r\n## Does it work with Traktor?\r\n\r\nNo. I thought the clue was in the name.\r\n\r\nI'm sure there's already an app which does this, or at least some of this,\r\nfor Traktor. To be honest, I'm out of touch with Traktor. \r\n\r\n## Can you make it work with Traktor?\r\n\r\nNo. I don't use Traktor, and I haven't used it in about 20 years. (That is\r\nquite a painful realisation to write down.)\r\n\r\n## Aww. Not even for me?\r\n\r\n_(This is not a frequently asked question)_\r\n\r\n## It seems to post Now Playing messages at weird times. How does it decide? How does it arrive at its \"best guess\"?\r\n\r\nThe main limitation to this app is what information we can get out of Serato. \r\nThe information we can get from Serato corresponds more or less to what is \r\nhappening in the \"History\" tab. Most importantly, the app learns most about \r\nwhat's going on **when a track is loaded or ejected**.\r\n\r\nOnce a track is loaded, a timer starts, and the track moves through three phases:\r\n* first 30 seconds: \"`loaded`\" \r\n* 30 seconds to half-the-song-length: \"`playing`\". (_eligible_ for a now\r\n  playing message, if another deck isn't playing.)\r\n* half-the-song-length - full-song-length: \"`scrobbleable`\". This state is\r\n  mainly for Last FM. the song will definitely be scrobbled.\r\n* any time after full-song-length: \"`finished`\". This song is treated as if it\r\n  was ejected.\r\n\r\nThe deck which is loaded first is considered first, and as long as it's still \r\nplaying (not empty, not finished), we don't move on to the next eligible song. \r\nAs soon as the track ends or is ejected, the next loaded deck is considered and\r\nthat's when a Now Playing event is sent.\r\n\r\n## It's posting the Now Playing message too early and giving the game away! How can I delay this?\r\n\r\nThe problem is really that the timer starts as soon as you load the song, even\r\nthough it will take you some time to bring it into the mix. So the \"now playing\"\r\nevent happens earlier than most people would like. It has no idea when you \r\nactually start playing, it has to guess from the time you load the track.\r\n\r\nCurrently your best option is to only load a track right before you want to play it.\r\n\r\nA future version will provide more options to address this, as it's annoying I agree.\r\n\r\n## It's posting the Now Playing message way too late - not until I load the next song!\r\n\r\nOnce you're done with a track, if you eject it off the deck, and that will make\r\nit obvious to the app which is the one that's playing.\r\n\r\n## It has a lot of messages on the screen which say WARNING. How bad is that?\r\n\r\nMost of the time you can ignore the screen. It will be hidden behind Serato\r\nanyway. You should check that it is doing what you need - is it tweeting to\r\ntwitter? Is it posting to your Discord? Can you find the nowplaying.txt file?\r\n\r\nAssuming it's all doing what you want, then you can basically ignore the \r\nmessages entirely.\r\n\r\nRead the messages if it's not doing what you want and then we can troubleshoot \r\nfrom there. There are some troubleshooting tips in the eponymous section of \r\nthis README.\r\n\r\n## Will it interfere with OBS? My stream overlay is resource hungry. \r\n\r\nThis doesn't run on your OBS machine, it runs on your Serato DJ machine. \r\nSo, no.\r\n\r\n## How much resource does it need to run? Does it use much memory?\r\n\r\nIt uses hardly any of either. About 4Mb of RAM (basically nothing) and\r\na tiny amount of CPU.\r\n\r\n## I want to use it but I'm scared of Terminal/Command Prompt.\r\n\r\nThat's not a question.\r\n\r\n## I want to use it but it looks too complicated.\r\n\r\nThat's also not a question.\r\n\r\n## Ok fine - what I'm really asking is how will I possibly ever remember how to use it when I have to type commands in?\r\n\r\nFirst of, I'd like to thank you for not asking the two non-questions above and\r\ngetting to the point.\r\n\r\nAs of 2020, there is now a actual app (with a nice \"vinyl\" icon) for macOS.\r\nIt's not pretty, but it will guide you through what to do.\r\n\r\nFor Windows users, I mean, look. The first time you set it up there are maybe\r\n5 steps, but every time you run it again in the future there's really only\r\n2 steps: open `cmd`, and then find where you wrote down how you started it\r\nlast time, and type it again. (Put it in a Notepad txt file and copy-paste\r\nit.) I believe in you!\r\n\r\n## It would be really great if you had a YouTube tutorial.\r\n\r\nI actually agree with you there. I should really do this some time, if only to\r\nmake a soothing point to people who have asked the first 2 questions. 😇\r\n\r\n## Can you show me how to set it up?\r\n\r\nSure!\r\n\r\n## Why on earth did you write it in PHP?\r\n\r\nSigh, not this question again. _*Walks out of interview*_\r\n\r\n## _*Runs after you*_ but WHY?\r\n\r\nLook, it was 2010, that's what I did in 2010. I'd never do such a ridiculous\r\nthing now, but I'm also done being embarrassed about it and have decided to\r\nlean into this monstrosity. It's really good code, go read it. Some of it's\r\nextremely over-engineered, and I hope it hurts your eyes.\r\n\r\nPlease feel free to rewrite the whole thing, I'll never get round to it.\r\n\r\nOh… although… having said that… I _am_ also the author of\r\n[CDJScrobbler](https://github.com/ben-xo/cdjscrobbler) which fundamentally\r\nworks in a very similar way, but is written in Java. So, that's probably how\r\nI'd do it, as Java, I've heard, has optional clicky buttons.\r\n\r\n# 8. CREDITS \u0026 LICENSE\r\n\r\n\r\nSSLScrobbler is Free Open Source Software by Ben XO.\r\nSSLScrobbler is licensed under the MIT license.\r\n\r\nSSLScrobbler includes the following external libraries:\r\n\r\n* php-growl (http://github.com/tylerhall/php-growl/)\r\n  originally by Tyler Hall, licensed under the MIT license.\r\n\r\n* PHP-Scrobbler (http://github.com/ben-xo/PHP-Scrobbler/)\r\n  originally by Mickael Desfrenes, licensed under the LGPL license.\r\n  \r\n* PHP Lastfm API (http://www.matto1990.com/projects/phplastfmapi/)\r\n  originally by Matt Oakes, licensed under the MIT license.\r\n  \r\n* Twitter OAuth (http://github.com/abraham/twitteroauth/)\r\n  originally by Abraham Williams, licensed under the MIT license.\r\n  \r\n* Twitter-PHP (https://github.com/dg/twitter-php)\r\n  originally by David Grudl, licensed under the New BSD license.\r\n  \r\n* getID3 (https://github.com/JamesHeinrich/getID3/)\r\n  originally by James Heinrich, licensed under the GPL license.\r\n  \r\n* vgd.php (https://v.gd/developers.php)\r\n  originally by Richard West, released into the public domain.\r\n\r\n* php-discord-sdk (https://github.com/cubiclesoft/php-discord-sdk)\r\n  originally by CubicleSoft, licensed under the MIT license.\r\n\r\n* vinyl.icns (https://findicons.com/icon/41917/vinyl)\r\n  originally by Nando Design Studio (Fernando Albuquerque), license Freeware Non-commercial\r\n\r\nThe \"NowPlaying\" plugin was originally, written for [Music Hack Day Boston](http://musichackdayboston.pbworks.com/w/page/31299401/sQRatchLive) but the demo functionality is years obsolete and was removed in 2022.\r\n\r\n\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fben-xo%2Fsslscrobbler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fben-xo%2Fsslscrobbler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fben-xo%2Fsslscrobbler/lists"}