-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
deduplication strategies added, version updated to 0.0.2
- Loading branch information
Showing
10 changed files
with
244 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,42 @@ | ||
# lastfm-tracks-dumper | ||
|
||
This cmd application obtains listened (aka *scrobbled*) tracks for a specific [Last.fm](https://www.last.fm/home) user | ||
and save it to .csv file. | ||
![lastfm_csv](docs/lastfm_header_new_400x92.png) | ||
|
||
### Usage | ||
The application obtains listened (aka *scrobbled*) tracks for a specific [Last.fm](https://www.last.fm/home) user | ||
and save it to a **.csv** file. The application allows to download all scrobbled tracks or duplicated as well. | ||
|
||
Run the .jar and pass two parameters as the arguments: | ||
### Usage | ||
|
||
1. Required Last.fm username | ||
2. Last.fm API token, see [here](https://www.last.fm/api#getting-started). | ||
Run the application **.jar** file and provide next parameters: | ||
- required username (`--user`) | ||
- API token, see [here](https://www.last.fm/api#getting-started) (`--token`) | ||
- download strategy (`--starategy`, optional): | ||
* `default` - get all scrobbled tracks | ||
* `only-duplicates` - get duplicated tracks without the 1st one (can be used for deduplication of the library) | ||
* `without-duplicates` - get only duplicated tracks (each duplicated track of the scrobbling history will be shown once) | ||
|
||
```shell | ||
|
||
java -jar lastfm-tracks-dumper-0.0.1-standalone.jar 'username' 'api_token' | ||
java -jar lastfm-tracks-dumper.jar --user %user% --token %token% --strategy default | ||
``` | ||
|
||
Currently only `date`, `artist`, `track` and `album` fields are saved to .csv. | ||
### Duplicates | ||
|
||
Due to scrobbling issues duplicated tracks can appear in the library 2 or more times. The application determine | ||
duplicates with two rules: | ||
1. Duplicated tracks go in sequential order. | ||
2. Difference in the scrobbled time is less than 5 sec. | ||
|
||
![duplicates](docs/duplicates_720x330.png) | ||
|
||
Depending on the strategy there will be different output result: | ||
* `only-duplicates` - track **Human** will appear 2 times, track **Be Mine** 1 time. | ||
* `without-duplicates` - each duplicated track will appear just once. | ||
|
||
Besides there is some [issue]( | ||
### Exported .csv data | ||
|
||
Currently only `date`, `artist`, `track` and `album` values are saved to .csv. | ||
|
||
Besides there is an [issue]( | ||
https://support.last.fm/t/invalid-mbids-in-responses-to-user-gettoptracks-and-user-getrecenttracks/2011) with *track/artist/album* ids and that's why this id data is not valuable right now. | ||
|
||
For `only-duplicates` strategy 2 more fields added: `page` and `pageLink` for easy navigation in the library. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
src/main/kotlin/com/unrec/lastfm/tracks/dumper/Constants.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.unrec.lastfm.tracks.dumper | ||
|
||
object Constants { | ||
|
||
const val userKey = "--user" | ||
const val tokenKey = "--token" | ||
const val strategyKey = "--strategy" | ||
|
||
const val baseUrl = "http://ws.audioscrobbler.com/2.0/" | ||
const val fetchPageSize = 200 | ||
const val defaultPageSize = 50 | ||
} |
35 changes: 35 additions & 0 deletions
35
src/main/kotlin/com/unrec/lastfm/tracks/dumper/CsvSchemas.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package com.unrec.lastfm.tracks.dumper | ||
|
||
import com.fasterxml.jackson.dataformat.csv.CsvSchema | ||
|
||
object CsvSchemas { | ||
|
||
private val defaultSchema: CsvSchema = CsvSchema.builder() | ||
.setColumnSeparator(';') | ||
.disableQuoteChar() | ||
.setUseHeader(true) | ||
.addColumn("date") | ||
.addColumn("artist") | ||
.addColumn("track") | ||
.addColumn("album") | ||
.build() | ||
|
||
private val schemaWithPages: CsvSchema = CsvSchema.builder() | ||
.setColumnSeparator(';') | ||
.disableQuoteChar() | ||
.setUseHeader(true) | ||
.addColumn("date") | ||
.addColumn("artist") | ||
.addColumn("track") | ||
.addColumn("album") | ||
.addColumn("page") | ||
.addColumn("pageLink") | ||
.build() | ||
|
||
val schemaMap = mapOf( | ||
defaultStrategy to defaultSchema, | ||
withoutDuplicatesStrategy to defaultSchema, | ||
duplicatesOnlyStrategy to schemaWithPages | ||
) | ||
} | ||
|
33 changes: 33 additions & 0 deletions
33
src/main/kotlin/com/unrec/lastfm/tracks/dumper/Strategies.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package com.unrec.lastfm.tracks.dumper | ||
|
||
import com.unrec.lastfm.tracks.dumper.model.Track | ||
|
||
val defaultStrategy = { list: List<Track> -> list } | ||
|
||
val withoutDuplicatesStrategy = { list: List<Track> -> list.removeAdjacentDuplicates() } | ||
|
||
val duplicatesOnlyStrategy = { list: List<Track> -> list.onlyDuplicates() } | ||
|
||
fun <T : Any> Iterable<T>.removeAdjacentDuplicates(): List<T> { | ||
var last: T? = null | ||
return mapNotNull { | ||
if (it == last) { | ||
null | ||
} else { | ||
last = it | ||
it | ||
} | ||
} | ||
} | ||
|
||
fun <T : Any> Iterable<T>.onlyDuplicates(): List<T> { | ||
return this.zipWithNext() | ||
.filter { it.first == it.second } | ||
.map { it.second } | ||
} | ||
|
||
val strategiesMap = mapOf( | ||
"default" to defaultStrategy, | ||
"without-duplicates" to withoutDuplicatesStrategy, | ||
"only-duplicates" to duplicatesOnlyStrategy | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
39 changes: 39 additions & 0 deletions
39
src/main/kotlin/com/unrec/lastfm/tracks/dumper/utils/AppUtils.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package com.unrec.lastfm.tracks.dumper.utils | ||
|
||
import com.unrec.lastfm.tracks.dumper.Constants.defaultPageSize | ||
import com.unrec.lastfm.tracks.dumper.Constants.strategyKey | ||
import com.unrec.lastfm.tracks.dumper.Constants.tokenKey | ||
import com.unrec.lastfm.tracks.dumper.Constants.userKey | ||
import kotlin.system.exitProcess | ||
|
||
fun Array<String>.asConfig(): Map<String, String> { | ||
|
||
if (this.size % 2 != 0) { | ||
println("Incorrect parameters are provided") | ||
exitProcess(1) | ||
} | ||
|
||
val map = this.toList().chunked(2).associate { it[0] to it[1] } | ||
|
||
if (!map.keys.contains(userKey)) { | ||
println("User is not specified") | ||
exitProcess(1) | ||
} | ||
|
||
if (!map.keys.contains(tokenKey)) { | ||
println("API token is not provided") | ||
exitProcess(1) | ||
} | ||
|
||
if (map[strategyKey] == null) { | ||
println("Strategy is not specified, tracks will not be filtered.") | ||
} | ||
|
||
return map | ||
} | ||
|
||
fun countPages(total: Int, pageSize: Int) = kotlin.math.ceil(total.toDouble() / pageSize).toInt() | ||
|
||
fun Int.toSitePage() = (this / defaultPageSize + 1) * defaultPageSize | ||
|
||
fun userPageUrl(user: String, page: Int) = "https://www.last.fm/user/$user/library?page=$page" |