Whole album matching and fingerprinting (#592)
* Cache result of GetAllArtists
* Fixed: Manual import not respecting album import notifications
* Fixed: partial album imports stay in queue, prompting manual import
* Fixed: Allow release if tracks are missing
* Fixed: Be tolerant of missing/extra "The" at start of artist name
* Improve manual import UI
* Omit video tracks from DB entirely
* Revert "faster test packaging in build.sh"
This reverts commit 2723e2a7b8
.
-u and -T are not supported on macOS
* Fix tests on linux and macOS
* Actually lint on linux
On linux yarn runs scripts with sh not bash so ** doesn't recursively glob
* Match whole albums
* Option to disable fingerprinting
* Rip out MediaInfo
* Don't split up things that have the same album selected in manual import
* Try to speed up IndentificationService
* More speedups
* Some fixes and increase power of recording id
* Fix NRE when no tags
* Fix NRE when some (but not all) files in a directory have missing tags
* Bump taglib, tidy up tag parsing
* Add a health check
* Remove media info setting
* Tags -> audioTags
* Add some tests where tags are null
* Rename history events
* Add missing method to interface
* Reinstate MediaInfo tags and update info with artist scan
Also adds migration to remove old format media info
* This file no longer exists
* Don't penalise year if missing from tags
* Formatting improvements
* Use correct system newline
* Switch to the netstandard2.0 library to support net 461
* TagLib.File is IDisposable so should be in a using
* Improve filename matching and add tests
* Neater logging of parsed tags
* Fix disk scan tests for new media info update
* Fix quality detection source
* Fix Inexact Artist/Album match
* Add button to clear track mapping
* Fix warning
* Pacify eslint
* Use \ not /
* Fix UI updates
* Fix media covers
Prevent localizing URL propaging back to the metadata object
* Reduce database overhead broadcasting UI updates
* Relax timings a bit to make test pass
* Remove irrelevant tests
* Test framework for identification service
* Fix PreferMissingToBadMatch test case
* Make fingerprinting more robust
* More logging
* Penalize unknown media format and country
* Prefer USA to UK
* Allow Data CD
* Fix exception if fingerprinting fails for all files
* Fix tests
* Fix NRE
* Allow apostrophes and remove accents in filename aggregation
* Address codacy issues
* Cope with old versions of fpcalc and suggest upgrade
* fpcalc health check passes if fingerprinting disabled
* Get the Artist meta with the artist
* Fix the mapper so that lazy loaded lists will be populated on Join
And therefore we can join TrackFiles on Tracks by default and avoid an
extra query
* Rename subtitle -> lyric
* Tidy up MediaInfoFormatter
pull/6/head
parent
8bf364945f
commit
bb02d73c42
@ -0,0 +1,65 @@
|
||||
.fileDetails {
|
||||
margin-bottom: 20px;
|
||||
border: 1px solid $borderColor;
|
||||
border-radius: 4px;
|
||||
background-color: $white;
|
||||
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.filename {
|
||||
flex-grow: 1;
|
||||
margin-right: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.expandButton {
|
||||
position: relative;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.actionButton {
|
||||
composes: button from 'Components/Link/IconButton.css';
|
||||
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.audioTags {
|
||||
padding-top: 15px;
|
||||
padding-bottom: 15px;
|
||||
border-top: 1px solid $borderColor;
|
||||
}
|
||||
|
||||
.expandButtonIcon {
|
||||
composes: actionButton;
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -12px;
|
||||
margin-left: -15px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.medium {
|
||||
border-right: 0;
|
||||
border-left: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.expandButtonIcon {
|
||||
position: static;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,258 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
||||
import Icon from 'Components/Icon';
|
||||
import Link from 'Components/Link/Link';
|
||||
import DescriptionList from 'Components/DescriptionList/DescriptionList';
|
||||
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
|
||||
import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionListItemTitle';
|
||||
import DescriptionListItemDescription from 'Components/DescriptionList/DescriptionListItemDescription';
|
||||
import styles from './FileDetails.css';
|
||||
|
||||
class FileDetails extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isExpanded: props.isExpanded
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onExpandPress = () => {
|
||||
const {
|
||||
isExpanded
|
||||
} = this.state;
|
||||
this.setState({ isExpanded: !isExpanded });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
renderRejections() {
|
||||
const {
|
||||
rejections
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<DescriptionListItemTitle>
|
||||
Rejections
|
||||
</DescriptionListItemTitle>
|
||||
{
|
||||
_.map(rejections, (item, key) => {
|
||||
return (
|
||||
<DescriptionListItemDescription key={key}>
|
||||
{item.reason}
|
||||
</DescriptionListItemDescription>
|
||||
);
|
||||
})
|
||||
}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
filename,
|
||||
audioTags,
|
||||
rejections
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isExpanded
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.fileDetails}
|
||||
>
|
||||
<div className={styles.header} onClick={this.onExpandPress}>
|
||||
<div className={styles.filename}>
|
||||
{filename}
|
||||
</div>
|
||||
|
||||
<div className={styles.expandButton}>
|
||||
<Icon
|
||||
className={styles.expandButtonIcon}
|
||||
name={isExpanded ? icons.COLLAPSE : icons.EXPAND}
|
||||
title={isExpanded ? 'Hide file info' : 'Show file info'}
|
||||
size={24}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{
|
||||
isExpanded &&
|
||||
<div className={styles.audioTags}>
|
||||
|
||||
<DescriptionList>
|
||||
{
|
||||
audioTags.title !== undefined &&
|
||||
<DescriptionListItem
|
||||
title="Track Title"
|
||||
data={audioTags.title}
|
||||
/>
|
||||
}
|
||||
{
|
||||
audioTags.trackNumbers[0] > 0 &&
|
||||
<DescriptionListItem
|
||||
title="Track Number"
|
||||
data={audioTags.trackNumbers[0]}
|
||||
/>
|
||||
}
|
||||
{
|
||||
audioTags.discNumber > 0 &&
|
||||
<DescriptionListItem
|
||||
title="Disc Number"
|
||||
data={audioTags.discNumber}
|
||||
/>
|
||||
}
|
||||
{
|
||||
audioTags.discCount > 0 &&
|
||||
<DescriptionListItem
|
||||
title="Disc Count"
|
||||
data={audioTags.discCount}
|
||||
/>
|
||||
}
|
||||
{
|
||||
audioTags.albumTitle !== undefined &&
|
||||
<DescriptionListItem
|
||||
title="Album"
|
||||
data={audioTags.albumTitle}
|
||||
/>
|
||||
}
|
||||
{
|
||||
audioTags.artistTitle !== undefined &&
|
||||
<DescriptionListItem
|
||||
title="Artist"
|
||||
data={audioTags.artistTitle}
|
||||
/>
|
||||
}
|
||||
{
|
||||
audioTags.country !== undefined &&
|
||||
<DescriptionListItem
|
||||
title="Country"
|
||||
data={audioTags.country.name}
|
||||
/>
|
||||
}
|
||||
{
|
||||
audioTags.year > 0 &&
|
||||
<DescriptionListItem
|
||||
title="Year"
|
||||
data={audioTags.year}
|
||||
/>
|
||||
}
|
||||
{
|
||||
audioTags.label !== undefined &&
|
||||
<DescriptionListItem
|
||||
title="Label"
|
||||
data={audioTags.label}
|
||||
/>
|
||||
}
|
||||
{
|
||||
audioTags.catalogNumber !== undefined &&
|
||||
<DescriptionListItem
|
||||
title="Catalog Number"
|
||||
data={audioTags.catalogNumber}
|
||||
/>
|
||||
}
|
||||
{
|
||||
audioTags.disambiguation !== undefined &&
|
||||
<DescriptionListItem
|
||||
title="Disambiguation"
|
||||
data={audioTags.disambiguation}
|
||||
/>
|
||||
}
|
||||
{
|
||||
audioTags.duration !== undefined &&
|
||||
<DescriptionListItem
|
||||
title="Duration"
|
||||
data={formatTimeSpan(audioTags.duration)}
|
||||
/>
|
||||
}
|
||||
{
|
||||
audioTags.artistMBId !== undefined &&
|
||||
<Link
|
||||
to={`https://musicbrainz.org/artist/${audioTags.artistMBId}`}
|
||||
>
|
||||
<DescriptionListItem
|
||||
title="MusicBrainz Artist ID"
|
||||
data={audioTags.artistMBId}
|
||||
/>
|
||||
</Link>
|
||||
}
|
||||
{
|
||||
audioTags.albumMBId !== undefined &&
|
||||
<Link
|
||||
to={`https://musicbrainz.org/release-group/${audioTags.albumMBId}`}
|
||||
>
|
||||
<DescriptionListItem
|
||||
title="MusicBrainz Album ID"
|
||||
data={audioTags.albumMBId}
|
||||
/>
|
||||
</Link>
|
||||
}
|
||||
{
|
||||
audioTags.releaseMBId !== undefined &&
|
||||
<Link
|
||||
to={`https://musicbrainz.org/release/${audioTags.releaseMBId}`}
|
||||
>
|
||||
<DescriptionListItem
|
||||
title="MusicBrainz Release ID"
|
||||
data={audioTags.releaseMBId}
|
||||
/>
|
||||
</Link>
|
||||
}
|
||||
{
|
||||
audioTags.recordingMBId !== undefined &&
|
||||
<Link
|
||||
to={`https://musicbrainz.org/recording/${audioTags.recordingMBId}`}
|
||||
>
|
||||
<DescriptionListItem
|
||||
title="MusicBrainz Recording ID"
|
||||
data={audioTags.recordingMBId}
|
||||
/>
|
||||
</Link>
|
||||
}
|
||||
{
|
||||
audioTags.trackMBId !== undefined &&
|
||||
<Link
|
||||
to={`https://musicbrainz.org/track/${audioTags.trackMBId}`}
|
||||
>
|
||||
<DescriptionListItem
|
||||
title="MusicBrainz Track ID"
|
||||
data={audioTags.trackMBId}
|
||||
/>
|
||||
</Link>
|
||||
}
|
||||
{
|
||||
rejections.length > 0 &&
|
||||
this.renderRejections()
|
||||
}
|
||||
</DescriptionList>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FileDetails.propTypes = {
|
||||
audioTags: PropTypes.object.isRequired,
|
||||
filename: PropTypes.string.isRequired,
|
||||
rejections: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
isExpanded: PropTypes.bool
|
||||
};
|
||||
|
||||
export default FileDetails;
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,194 @@
|
||||
{
|
||||
"expectedMusicBrainzReleaseIds": [
|
||||
"0ce2d66f-e871-415a-9a85-e564f99d4021"
|
||||
],
|
||||
"metadataProfile": {
|
||||
"name": "Standard",
|
||||
"primaryAlbumTypes": [
|
||||
{
|
||||
"primaryAlbumType": {
|
||||
"id": 2,
|
||||
"name": "Single"
|
||||
},
|
||||
"allowed": true
|
||||
},
|
||||
{
|
||||
"primaryAlbumType": {
|
||||
"id": 4,
|
||||
"name": "Other"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"primaryAlbumType": {
|
||||
"id": 1,
|
||||
"name": "EP"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"primaryAlbumType": {
|
||||
"id": 3,
|
||||
"name": "Broadcast"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"primaryAlbumType": {
|
||||
"id": 0,
|
||||
"name": "Album"
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
],
|
||||
"secondaryAlbumTypes": [
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 0,
|
||||
"name": "Studio"
|
||||
},
|
||||
"allowed": true
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 3,
|
||||
"name": "Spokenword"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 2,
|
||||
"name": "Soundtrack"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 7,
|
||||
"name": "Remix"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 9,
|
||||
"name": "Mixtape/Street"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 6,
|
||||
"name": "Live"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 4,
|
||||
"name": "Interview"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 8,
|
||||
"name": "DJ-mix"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 10,
|
||||
"name": "Demo"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 1,
|
||||
"name": "Compilation"
|
||||
},
|
||||
"allowed": false
|
||||
}
|
||||
],
|
||||
"releaseStatuses": [
|
||||
{
|
||||
"releaseStatus": {
|
||||
"id": 3,
|
||||
"name": "Pseudo"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"releaseStatus": {
|
||||
"id": 1,
|
||||
"name": "Promotion"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"releaseStatus": {
|
||||
"id": 0,
|
||||
"name": "Official"
|
||||
},
|
||||
"allowed": true
|
||||
},
|
||||
{
|
||||
"releaseStatus": {
|
||||
"id": 2,
|
||||
"name": "Bootleg"
|
||||
},
|
||||
"allowed": false
|
||||
}
|
||||
],
|
||||
"id": 1
|
||||
},
|
||||
"artist": "7ac055fa-e357-4890-9098-010b8094a900",
|
||||
"newDownload": false,
|
||||
"singleRelease": false,
|
||||
"tracks": [
|
||||
{
|
||||
"path": "D:\\Test2\\Alabama\\The Touch\\06 Touch Me When We're Dancing.mp3",
|
||||
"fileTrackInfo": {
|
||||
"title": "Touch Me When We're Dancing",
|
||||
"cleanTitle": "Touch Me When We're Dancing",
|
||||
"artistTitle": "Alabama",
|
||||
"albumTitle": "The Touch",
|
||||
"artistTitleInfo": {
|
||||
"title": "Alabama",
|
||||
"year": 1986
|
||||
},
|
||||
"discNumber": 0,
|
||||
"discCount": 0,
|
||||
"year": 1986,
|
||||
"duration": "00:03:43.2950000",
|
||||
"quality": {
|
||||
"quality": {
|
||||
"id": 2,
|
||||
"name": "MP3-VBR-V0"
|
||||
},
|
||||
"revision": {
|
||||
"version": 1,
|
||||
"real": 0
|
||||
}
|
||||
},
|
||||
"mediaInfo": {
|
||||
"audioFormat": "MPEG Version 1 Audio, Layer 3 VBR",
|
||||
"audioBitrate": 161,
|
||||
"audioChannels": 2,
|
||||
"audioBits": 0,
|
||||
"audioSampleRate": 44100
|
||||
},
|
||||
"trackNumbers": [
|
||||
6
|
||||
],
|
||||
"language": {
|
||||
"id": 1,
|
||||
"name": "English"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,235 @@
|
||||
{
|
||||
"expectedMusicBrainzReleaseIds": [
|
||||
"25f0fa1b-ae04-479a-a182-18a655ff6040"
|
||||
],
|
||||
"metadataProfile": {
|
||||
"name": "Album+Single",
|
||||
"primaryAlbumTypes": [
|
||||
{
|
||||
"primaryAlbumType": {
|
||||
"id": 4,
|
||||
"name": "Other"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"primaryAlbumType": {
|
||||
"id": 3,
|
||||
"name": "Broadcast"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"primaryAlbumType": {
|
||||
"id": 2,
|
||||
"name": "Single"
|
||||
},
|
||||
"allowed": true
|
||||
},
|
||||
{
|
||||
"primaryAlbumType": {
|
||||
"id": 1,
|
||||
"name": "EP"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"primaryAlbumType": {
|
||||
"id": 0,
|
||||
"name": "Album"
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
],
|
||||
"secondaryAlbumTypes": [
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 10,
|
||||
"name": "Demo"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 9,
|
||||
"name": "Mixtape/Street"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 8,
|
||||
"name": "DJ-mix"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 7,
|
||||
"name": "Remix"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 6,
|
||||
"name": "Live"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 4,
|
||||
"name": "Interview"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 3,
|
||||
"name": "Spokenword"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 2,
|
||||
"name": "Soundtrack"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 1,
|
||||
"name": "Compilation"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 0,
|
||||
"name": "Studio"
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
],
|
||||
"releaseStatuses": [
|
||||
{
|
||||
"releaseStatus": {
|
||||
"id": 3,
|
||||
"name": "Pseudo"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"releaseStatus": {
|
||||
"id": 2,
|
||||
"name": "Bootleg"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"releaseStatus": {
|
||||
"id": 1,
|
||||
"name": "Promotion"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"releaseStatus": {
|
||||
"id": 0,
|
||||
"name": "Official"
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
],
|
||||
"id": 2
|
||||
},
|
||||
"artist": "70248960-cb53-4ea4-943a-edb18f7d336f",
|
||||
"newDownload": true,
|
||||
"singleRelease": false,
|
||||
"tracks": [
|
||||
{
|
||||
"path": "/mnt/data1/LidarrTest/Bruce Springsteen/Album/10_Glory_Days.mp3",
|
||||
"fileTrackInfo": {
|
||||
"title": "Glory Days",
|
||||
"cleanTitle": "Glory Days",
|
||||
"artistTitle": "Bruce Springsteen",
|
||||
"albumTitle": "Born in the U.S.A.",
|
||||
"artistTitleInfo": {
|
||||
"title": "Bruce Springsteen",
|
||||
"year": 1984
|
||||
},
|
||||
"discNumber": 0,
|
||||
"discCount": 0,
|
||||
"year": 1984,
|
||||
"duration": "00:04:18.0680000",
|
||||
"quality": {
|
||||
"quality": {
|
||||
"id": 1,
|
||||
"name": "MP3-192"
|
||||
},
|
||||
"revision": {
|
||||
"version": 1,
|
||||
"real": 0
|
||||
}
|
||||
},
|
||||
"mediaInfo": {
|
||||
"audioFormat": "MPEG Version 1 Audio, Layer 3",
|
||||
"audioBitrate": 192,
|
||||
"audioChannels": 2,
|
||||
"audioBits": 0,
|
||||
"audioSampleRate": 44100
|
||||
},
|
||||
"trackNumbers": [
|
||||
10
|
||||
],
|
||||
"language": {
|
||||
"id": 1,
|
||||
"name": "English"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/mnt/data1/LidarrTest/Bruce Springsteen/Album/11_Dancing_In_The_Dark.mp3",
|
||||
"fileTrackInfo": {
|
||||
"title": "Dancing In The Dark",
|
||||
"cleanTitle": "Dancing In The Dark",
|
||||
"artistTitle": "Bruce Springsteen",
|
||||
"albumTitle": "Born in the U.S.A.",
|
||||
"artistTitleInfo": {
|
||||
"title": "Bruce Springsteen",
|
||||
"year": 1984
|
||||
},
|
||||
"discNumber": 0,
|
||||
"discCount": 0,
|
||||
"year": 1984,
|
||||
"duration": "00:04:03.0450000",
|
||||
"quality": {
|
||||
"quality": {
|
||||
"id": 1,
|
||||
"name": "MP3-192"
|
||||
},
|
||||
"revision": {
|
||||
"version": 1,
|
||||
"real": 0
|
||||
}
|
||||
},
|
||||
"mediaInfo": {
|
||||
"audioFormat": "MPEG Version 1 Audio, Layer 3",
|
||||
"audioBitrate": 192,
|
||||
"audioChannels": 2,
|
||||
"audioBits": 0,
|
||||
"audioSampleRate": 44100
|
||||
},
|
||||
"trackNumbers": [
|
||||
11
|
||||
],
|
||||
"language": {
|
||||
"id": 1,
|
||||
"name": "English"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,573 @@
|
||||
{
|
||||
"expectedMusicBrainzReleaseIds": [
|
||||
"4e2dd34f-53fe-4d54-b564-b14a2871505e"
|
||||
],
|
||||
"metadataProfile": {
|
||||
"name": "Standard",
|
||||
"primaryAlbumTypes": [
|
||||
{
|
||||
"primaryAlbumType": {
|
||||
"id": 2,
|
||||
"name": "Single"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"primaryAlbumType": {
|
||||
"id": 4,
|
||||
"name": "Other"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"primaryAlbumType": {
|
||||
"id": 1,
|
||||
"name": "EP"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"primaryAlbumType": {
|
||||
"id": 3,
|
||||
"name": "Broadcast"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"primaryAlbumType": {
|
||||
"id": 0,
|
||||
"name": "Album"
|
||||
},
|
||||
"allowed": true
|
||||
}
|
||||
],
|
||||
"secondaryAlbumTypes": [
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 0,
|
||||
"name": "Studio"
|
||||
},
|
||||
"allowed": true
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 3,
|
||||
"name": "Spokenword"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 2,
|
||||
"name": "Soundtrack"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 7,
|
||||
"name": "Remix"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 9,
|
||||
"name": "Mixtape/Street"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 6,
|
||||
"name": "Live"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 4,
|
||||
"name": "Interview"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 8,
|
||||
"name": "DJ-mix"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 10,
|
||||
"name": "Demo"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"secondaryAlbumType": {
|
||||
"id": 1,
|
||||
"name": "Compilation"
|
||||
},
|
||||
"allowed": false
|
||||
}
|
||||
],
|
||||
"releaseStatuses": [
|
||||
{
|
||||
"releaseStatus": {
|
||||
"id": 3,
|
||||
"name": "Pseudo"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"releaseStatus": {
|
||||
"id": 1,
|
||||
"name": "Promotion"
|
||||
},
|
||||
"allowed": false
|
||||
},
|
||||
{
|
||||
"releaseStatus": {
|
||||
"id": 0,
|
||||
"name": "Official"
|
||||
},
|
||||
"allowed": true
|
||||
},
|
||||
{
|
||||
"releaseStatus": {
|
||||
"id": 2,
|
||||
"name": "Bootleg"
|
||||
},
|
||||
"allowed": false
|
||||
}
|
||||
],
|
||||
"id": 1
|
||||
},
|
||||
"artist": "6fe07aa5-fec0-4eca-a456-f29bff451b04",
|
||||
"newDownload": false,
|
||||
"singleRelease": false,
|
||||
"tracks": [
|
||||
{
|
||||
"path": "/mnt/data1/LidarrTest/Weezer/Weezer (2019)/01-weezer-africa-0f640cbf.mp3",
|
||||
"fileTrackInfo": {
|
||||
"title": "Africa",
|
||||
"cleanTitle": "Africa",
|
||||
"artistTitle": "Weezer",
|
||||
"albumTitle": "Weezer (Teal Album)",
|
||||
"artistTitleInfo": {
|
||||
"title": "Weezer",
|
||||
"year": 2019
|
||||
},
|
||||
"discNumber": 0,
|
||||
"discCount": 0,
|
||||
"year": 2019,
|
||||
"label": "Crush Music / Atlantic ",
|
||||
"duration": "00:03:58.6850000",
|
||||
"quality": {
|
||||
"quality": {
|
||||
"id": 4,
|
||||
"name": "MP3-320"
|
||||
},
|
||||
"revision": {
|
||||
"version": 1,
|
||||
"real": 0
|
||||
}
|
||||
},
|
||||
"mediaInfo": {
|
||||
"audioFormat": "MPEG Version 1 Audio, Layer 3",
|
||||
"audioBitrate": 320,
|
||||
"audioChannels": 2,
|
||||
"audioBits": 0,
|
||||
"audioSampleRate": 44100
|
||||
},
|
||||
"trackNumbers": [
|
||||
1
|
||||
],
|
||||
"language": {
|
||||
"id": 1,
|
||||
"name": "English"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/mnt/data1/LidarrTest/Weezer/Weezer (2019)/02-weezer-everybody_wants_to_rule_the_world-efc2a5b4.mp3",
|
||||
"fileTrackInfo": {
|
||||
"title": "Everybody Wants To Rule The World",
|
||||
"cleanTitle": "Everybody Wants To Rule The World",
|
||||
"artistTitle": "Weezer",
|
||||
"albumTitle": "Weezer (Teal Album)",
|
||||
"artistTitleInfo": {
|
||||
"title": "Weezer",
|
||||
"year": 2019
|
||||
},
|
||||
"discNumber": 0,
|
||||
"discCount": 0,
|
||||
"year": 2019,
|
||||
"label": "Crush Music / Atlantic ",
|
||||
"duration": "00:04:04.8960000",
|
||||
"quality": {
|
||||
"quality": {
|
||||
"id": 4,
|
||||
"name": "MP3-320"
|
||||
},
|
||||
"revision": {
|
||||
"version": 1,
|
||||
"real": 0
|
||||
}
|
||||
},
|
||||
"mediaInfo": {
|
||||
"audioFormat": "MPEG Version 1 Audio, Layer 3",
|
||||
"audioBitrate": 320,
|
||||
"audioChannels": 2,
|
||||
"audioBits": 0,
|
||||
"audioSampleRate": 44100
|
||||
},
|
||||
"trackNumbers": [
|
||||
2
|
||||
],
|
||||
"language": {
|
||||
"id": 1,
|
||||
"name": "English"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/mnt/data1/LidarrTest/Weezer/Weezer (2019)/03-weezer-sweet_dreams_(are_made_of_this)-a8a934a6.mp3",
|
||||
"fileTrackInfo": {
|
||||
"title": "Sweet Dreams (Are Made Of This)",
|
||||
"cleanTitle": "Sweet Dreams (Are Made Of This)",
|
||||
"artistTitle": "Weezer",
|
||||
"albumTitle": "Weezer (Teal Album)",
|
||||
"artistTitleInfo": {
|
||||
"title": "Weezer",
|
||||
"year": 2019
|
||||
},
|
||||
"discNumber": 0,
|
||||
"discCount": 0,
|
||||
"year": 2019,
|
||||
"label": "Crush Music / Atlantic ",
|
||||
"duration": "00:03:34.8550000",
|
||||
"quality": {
|
||||
"quality": {
|
||||
"id": 4,
|
||||
"name": "MP3-320"
|
||||
},
|
||||
"revision": {
|
||||
"version": 1,
|
||||
"real": 0
|
||||
}
|
||||
},
|
||||
"mediaInfo": {
|
||||
"audioFormat": "MPEG Version 1 Audio, Layer 3",
|
||||
"audioBitrate": 320,
|
||||
"audioChannels": 2,
|
||||
"audioBits": 0,
|
||||
"audioSampleRate": 44100
|
||||
},
|
||||
"trackNumbers": [
|
||||
3
|
||||
],
|
||||
"language": {
|
||||
"id": 1,
|
||||
"name": "English"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/mnt/data1/LidarrTest/Weezer/Weezer (2019)/04-weezer-take_on_me-5698a04c.mp3",
|
||||
"fileTrackInfo": {
|
||||
"title": "Take On Me",
|
||||
"cleanTitle": "Take On Me",
|
||||
"artistTitle": "Weezer",
|
||||
"albumTitle": "Weezer (Teal Album)",
|
||||
"artistTitleInfo": {
|
||||
"title": "Weezer",
|
||||
"year": 2019
|
||||
},
|
||||
"discNumber": 0,
|
||||
"discCount": 0,
|
||||
"year": 2019,
|
||||
"label": "Crush Music / Atlantic ",
|
||||
"duration": "00:03:43.6510000",
|
||||
"quality": {
|
||||
"quality": {
|
||||
"id": 4,
|
||||
"name": "MP3-320"
|
||||
},
|
||||
"revision": {
|
||||
"version": 1,
|
||||
"real": 0
|
||||
}
|
||||
},
|
||||
"mediaInfo": {
|
||||
"audioFormat": "MPEG Version 1 Audio, Layer 3",
|
||||
"audioBitrate": 320,
|
||||
"audioChannels": 2,
|
||||
"audioBits": 0,
|
||||
"audioSampleRate": 44100
|
||||
},
|
||||
"trackNumbers": [
|
||||
4
|
||||
],
|
||||
"language": {
|
||||
"id": 1,
|
||||
"name": "English"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/mnt/data1/LidarrTest/Weezer/Weezer (2019)/05-weezer-happy_together-dd30d8d4.mp3",
|
||||
"fileTrackInfo": {
|
||||
"title": "Happy Together",
|
||||
"cleanTitle": "Happy Together",
|
||||
"artistTitle": "Weezer",
|
||||
"albumTitle": "Weezer (Teal Album)",
|
||||
"artistTitleInfo": {
|
||||
"title": "Weezer",
|
||||
"year": 2019
|
||||
},
|
||||
"discNumber": 0,
|
||||
"discCount": 0,
|
||||
"year": 2019,
|
||||
"label": "Crush Music / Atlantic ",
|
||||
"duration": "00:02:25.7160000",
|
||||
"quality": {
|
||||
"quality": {
|
||||
"id": 4,
|
||||
"name": "MP3-320"
|
||||
},
|
||||
"revision": {
|
||||
"version": 1,
|
||||
"real": 0
|
||||
}
|
||||
},
|
||||
"mediaInfo": {
|
||||
"audioFormat": "MPEG Version 1 Audio, Layer 3",
|
||||
"audioBitrate": 320,
|
||||
"audioChannels": 2,
|
||||
"audioBits": 0,
|
||||
"audioSampleRate": 44100
|
||||
},
|
||||
"trackNumbers": [
|
||||
5
|
||||
],
|
||||
"language": {
|
||||
"id": 1,
|
||||
"name": "English"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/mnt/data1/LidarrTest/Weezer/Weezer (2019)/06-weezer-paranoid-d0617671.mp3",
|
||||
"fileTrackInfo": {
|
||||
"title": "Paranoid",
|
||||
"cleanTitle": "Paranoid",
|
||||
"artistTitle": "Weezer",
|
||||
"albumTitle": "Weezer (Teal Album)",
|
||||
"artistTitleInfo": {
|
||||
"title": "Weezer",
|
||||
"year": 2019
|
||||
},
|
||||
"discNumber": 0,
|
||||
"discCount": 0,
|
||||
"year": 2019,
|
||||
"label": "Crush Music / Atlantic ",
|
||||
"duration": "00:02:44.9260000",
|
||||
"quality": {
|
||||
"quality": {
|
||||
"id": 4,
|
||||
"name": "MP3-320"
|
||||
},
|
||||
"revision": {
|
||||
"version": 1,
|
||||
"real": 0
|
||||
}
|
||||
},
|
||||
"mediaInfo": {
|
||||
"audioFormat": "MPEG Version 1 Audio, Layer 3",
|
||||
"audioBitrate": 320,
|
||||
"audioChannels": 2,
|
||||
"audioBits": 0,
|
||||
"audioSampleRate": 44100
|
||||
},
|
||||
"trackNumbers": [
|
||||
6
|
||||
],
|
||||
"language": {
|
||||
"id": 1,
|
||||
"name": "English"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/mnt/data1/LidarrTest/Weezer/Weezer (2019)/07-weezer-mr_blue_sky-e3e44f02.mp3",
|
||||
"fileTrackInfo": {
|
||||
"title": "Mr. Blue Sky",
|
||||
"cleanTitle": "Mr. Blue Sky",
|
||||
"artistTitle": "Weezer",
|
||||
"albumTitle": "Weezer (Teal Album)",
|
||||
"artistTitleInfo": {
|
||||
"title": "Weezer",
|
||||
"year": 2019
|
||||
},
|
||||
"discNumber": 0,
|
||||
"discCount": 0,
|
||||
"year": 2019,
|
||||
"label": "Crush Music / Atlantic ",
|
||||
"duration": "00:04:46.4210000",
|
||||
"quality": {
|
||||
"quality": {
|
||||
"id": 4,
|
||||
"name": "MP3-320"
|
||||
},
|
||||
"revision": {
|
||||
"version": 1,
|
||||
"real": 0
|
||||
}
|
||||
},
|
||||
"mediaInfo": {
|
||||
"audioFormat": "MPEG Version 1 Audio, Layer 3",
|
||||
"audioBitrate": 320,
|
||||
"audioChannels": 2,
|
||||
"audioBits": 0,
|
||||
"audioSampleRate": 44100
|
||||
},
|
||||
"trackNumbers": [
|
||||
7
|
||||
],
|
||||
"language": {
|
||||
"id": 1,
|
||||
"name": "English"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/mnt/data1/LidarrTest/Weezer/Weezer (2019)/08-weezer-no_scrubs-577fa9fa.mp3",
|
||||
"fileTrackInfo": {
|
||||
"title": "No Scrubs",
|
||||
"cleanTitle": "No Scrubs",
|
||||
"artistTitle": "Weezer",
|
||||
"albumTitle": "Weezer (Teal Album)",
|
||||
"artistTitleInfo": {
|
||||
"title": "Weezer",
|
||||
"year": 2019
|
||||
},
|
||||
"discNumber": 0,
|
||||
"discCount": 0,
|
||||
"year": 2019,
|
||||
"label": "Crush Music / Atlantic ",
|
||||
"duration": "00:03:10.3730000",
|
||||
"quality": {
|
||||
"quality": {
|
||||
"id": 4,
|
||||
"name": "MP3-320"
|
||||
},
|
||||
"revision": {
|
||||
"version": 1,
|
||||
"real": 0
|
||||
}
|
||||
},
|
||||
"mediaInfo": {
|
||||
"audioFormat": "MPEG Version 1 Audio, Layer 3",
|
||||
"audioBitrate": 320,
|
||||
"audioChannels": 2,
|
||||
"audioBits": 0,
|
||||
"audioSampleRate": 44100
|
||||
},
|
||||
"trackNumbers": [
|
||||
8
|
||||
],
|
||||
"language": {
|
||||
"id": 1,
|
||||
"name": "English"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/mnt/data1/LidarrTest/Weezer/Weezer (2019)/09-weezer-billie_jean-9c35bbda.mp3",
|
||||
"fileTrackInfo": {
|
||||
"title": "Billie Jean",
|
||||
"cleanTitle": "Billie Jean",
|
||||
"artistTitle": "Weezer",
|
||||
"albumTitle": "Weezer (Teal Album)",
|
||||
"artistTitleInfo": {
|
||||
"title": "Weezer",
|
||||
"year": 2019
|
||||
},
|
||||
"discNumber": 0,
|
||||
"discCount": 0,
|
||||
"year": 2019,
|
||||
"label": "Crush Music / Atlantic ",
|
||||
"duration": "00:04:54.1990000",
|
||||
"quality": {
|
||||
"quality": {
|
||||
"id": 4,
|
||||
"name": "MP3-320"
|
||||
},
|
||||
"revision": {
|
||||
"version": 1,
|
||||
"real": 0
|
||||
}
|
||||
},
|
||||
"mediaInfo": {
|
||||
"audioFormat": "MPEG Version 1 Audio, Layer 3",
|
||||
"audioBitrate": 320,
|
||||
"audioChannels": 2,
|
||||
"audioBits": 0,
|
||||
"audioSampleRate": 44100
|
||||
},
|
||||
"trackNumbers": [
|
||||
9
|
||||
],
|
||||
"language": {
|
||||
"id": 1,
|
||||
"name": "English"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "/mnt/data1/LidarrTest/Weezer/Weezer (2019)/10-weezer-stand_by_me-396f336f.mp3",
|
||||
"fileTrackInfo": {
|
||||
"title": "Stand By Me",
|
||||
"cleanTitle": "Stand By Me",
|
||||
"artistTitle": "Weezer",
|
||||
"albumTitle": "Weezer (Teal Album)",
|
||||
"artistTitleInfo": {
|
||||
"title": "Weezer",
|
||||
"year": 2019
|
||||
},
|
||||
"discNumber": 0,
|
||||
"discCount": 0,
|
||||
"year": 2019,
|
||||
"label": "Crush Music / Atlantic ",
|
||||
"duration": "00:03:00.9770000",
|
||||
"quality": {
|
||||
"quality": {
|
||||
"id": 4,
|
||||
"name": "MP3-320"
|
||||
},
|
||||
"revision": {
|
||||
"version": 1,
|
||||
"real": 0
|
||||
}
|
||||
},
|
||||
"mediaInfo": {
|
||||
"audioFormat": "MPEG Version 1 Audio, Layer 3",
|
||||
"audioBitrate": 320,
|
||||
"audioChannels": 2,
|
||||
"audioBits": 0,
|
||||
"audioSampleRate": 44100
|
||||
},
|
||||
"trackNumbers": [
|
||||
10
|
||||
],
|
||||
"language": {
|
||||
"id": 1,
|
||||
"name": "English"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
Binary file not shown.
Binary file not shown.
@ -1,149 +0,0 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class FormatAudioChannelsFixture : TestBase
|
||||
{
|
||||
[Test]
|
||||
public void should_subtract_one_from_AudioChannels_as_total_channels_if_LFE_in_AudioChannelPositionsText()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 6,
|
||||
AudioChannelPositions = null,
|
||||
AudioChannelPositionsText = "Front: L C R, Side: L R, LFE"
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_AudioChannels_as_total_channels_if_LFE_not_in_AudioChannelPositionsText()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 2,
|
||||
AudioChannelPositions = null,
|
||||
AudioChannelPositionsText = "Front: L R"
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_0_if_schema_revision_is_less_than_3_and_other_properties_are_null()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 2,
|
||||
AudioChannelPositions = null,
|
||||
AudioChannelPositionsText = null,
|
||||
SchemaRevision = 2
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_AudioChannels_if_schema_revision_is_3_and_other_properties_are_null()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 2,
|
||||
AudioChannelPositions = null,
|
||||
AudioChannelPositionsText = null,
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_sum_AudioChannelPositions()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 2,
|
||||
AudioChannelPositions = "2/0/0",
|
||||
AudioChannelPositionsText = null,
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_sum_AudioChannelPositions_including_decimal()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 2,
|
||||
AudioChannelPositions = "3/2/0.1",
|
||||
AudioChannelPositionsText = null,
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(5.1m);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_cleanup_extraneous_text_from_AudioChannelPositions()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 2,
|
||||
AudioChannelPositions = "Object Based / 3/2/2.1",
|
||||
AudioChannelPositionsText = null,
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_skip_empty_groups_in_AudioChannelPositions()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 2,
|
||||
AudioChannelPositions = " / 2/0/0.0",
|
||||
AudioChannelPositionsText = null,
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_sum_first_series_of_numbers_from_AudioChannelPositions()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 2,
|
||||
AudioChannelPositions = "3/2/2.1 / 3/2/2.1",
|
||||
AudioChannelPositionsText = null,
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(7.1m);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_sum_dual_mono_representation_AudioChannelPositions()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioChannels = 2,
|
||||
AudioChannelPositions = "1+1",
|
||||
AudioChannelPositionsText = null,
|
||||
SchemaRevision = 3
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioChannels(mediaInfoModel).Should().Be(2.0m);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo.MediaInfoFormatterTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class FormatAudioCodecFixture : TestBase
|
||||
{
|
||||
[TestCase("AC-3", "AC3")]
|
||||
[TestCase("E-AC-3", "EAC3")]
|
||||
[TestCase("MPEG Audio", "MPEG Audio")]
|
||||
[TestCase("DTS", "DTS")]
|
||||
public void should_format_audio_format(string audioFormat, string expectedFormat)
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioFormat = audioFormat
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel).Should().Be(expectedFormat);
|
||||
}
|
||||
|
||||
[TestCase("MPEG Audio, A_MPEG/L2, , ", "droned.s01e03.swedish.720p.hdtv.x264-prince", "MP2")]
|
||||
[TestCase("Vorbis, A_VORBIS, , Xiph.Org libVorbis I 20101101 (Schaufenugget)", "DB Super HDTV", "Vorbis")]
|
||||
[TestCase("PCM, 1, , ", "DW DVDRip XviD-idTV", "PCM")] // Dubbed most likely
|
||||
[TestCase("TrueHD, A_TRUEHD, , ", "", "TrueHD")]
|
||||
[TestCase("WMA, 161, , ", "Droned.wmv", "WMA")]
|
||||
[TestCase("WMA, 162, Pro, ", "B.N.S04E18.720p.WEB-DL", "WMA")]
|
||||
public void should_format_audio_format(string audioFormatPack, string sceneName, string expectedFormat)
|
||||
{
|
||||
var split = audioFormatPack.Split(new string[] { ", " }, System.StringSplitOptions.None);
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioFormat = split[0],
|
||||
AudioCodecID = split[1],
|
||||
AudioProfile = split[2],
|
||||
AudioCodecLibrary = split[3]
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel).Should().Be(expectedFormat);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_MP3_for_MPEG_Audio_with_Layer_3_for_the_profile()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioFormat = "MPEG Audio",
|
||||
AudioProfile = "Layer 3"
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel).Should().Be("MP3");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_AudioFormat_by_default()
|
||||
{
|
||||
var mediaInfoModel = new MediaInfoModel
|
||||
{
|
||||
AudioFormat = "Other Audio Format",
|
||||
AudioCodecID = "Other Audio Codec"
|
||||
};
|
||||
|
||||
MediaInfoFormatter.FormatAudioCodec(mediaInfoModel).Should().Be(mediaInfoModel.AudioFormat);
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,184 +0,0 @@
|
||||
using System.IO;
|
||||
using FizzWare.NBuilder;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Test.Common;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
||||
{
|
||||
[TestFixture]
|
||||
public class UpdateMediaInfoServiceFixture : CoreTest<UpdateMediaInfoService>
|
||||
{
|
||||
private Artist _artist;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_artist = new Artist
|
||||
{
|
||||
Id = 1,
|
||||
Path = @"C:\artist".AsOsAgnostic()
|
||||
};
|
||||
|
||||
Mocker.GetMock<IConfigService>()
|
||||
.SetupGet(s => s.EnableMediaInfo)
|
||||
.Returns(true);
|
||||
}
|
||||
|
||||
private void GivenFileExists()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.FileExists(It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
}
|
||||
|
||||
private void GivenSuccessfulScan()
|
||||
{
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Setup(v => v.GetMediaInfo(It.IsAny<string>()))
|
||||
.Returns(new MediaInfoModel());
|
||||
}
|
||||
|
||||
private void GivenFailedScan(string path)
|
||||
{
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Setup(v => v.GetMediaInfo(path))
|
||||
.Returns((MediaInfoModel)null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_skip_up_to_date_media_info()
|
||||
{
|
||||
var trackFiles = Builder<TrackFile>.CreateListOfSize(3)
|
||||
.All()
|
||||
.With(v => v.RelativePath = "media.flac")
|
||||
.TheFirst(1)
|
||||
.With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = VideoFileInfoReader.CURRENT_MEDIA_INFO_SCHEMA_REVISION })
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(v => v.GetFilesByArtist(1))
|
||||
.Returns(trackFiles);
|
||||
|
||||
GivenFileExists();
|
||||
GivenSuccessfulScan();
|
||||
|
||||
Subject.Handle(new ArtistScannedEvent(_artist));
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.flac")), Times.Exactly(2));
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(v => v.Update(It.IsAny<TrackFile>()), Times.Exactly(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_skip_not_yet_date_media_info()
|
||||
{
|
||||
var trackFiles = Builder<TrackFile>.CreateListOfSize(3)
|
||||
.All()
|
||||
.With(v => v.RelativePath = "media.flac")
|
||||
.TheFirst(1)
|
||||
.With(v => v.MediaInfo = new MediaInfoModel { SchemaRevision = VideoFileInfoReader.MINIMUM_MEDIA_INFO_SCHEMA_REVISION })
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(v => v.GetFilesByArtist(1))
|
||||
.Returns(trackFiles);
|
||||
|
||||
GivenFileExists();
|
||||
GivenSuccessfulScan();
|
||||
|
||||
Subject.Handle(new ArtistScannedEvent(_artist));
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.flac")), Times.Exactly(2));
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(v => v.Update(It.IsAny<TrackFile>()), Times.Exactly(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_update_outdated_media_info()
|
||||
{
|
||||
var trackFiles = Builder<TrackFile>.CreateListOfSize(3)
|
||||
.All()
|
||||
.With(v => v.RelativePath = "media.flac")
|
||||
.TheFirst(1)
|
||||
.With(v => v.MediaInfo = new MediaInfoModel())
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(v => v.GetFilesByArtist(1))
|
||||
.Returns(trackFiles);
|
||||
|
||||
GivenFileExists();
|
||||
GivenSuccessfulScan();
|
||||
|
||||
Subject.Handle(new ArtistScannedEvent(_artist));
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.flac")), Times.Exactly(3));
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(v => v.Update(It.IsAny<TrackFile>()), Times.Exactly(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_ignore_missing_files()
|
||||
{
|
||||
var trackFiles = Builder<TrackFile>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(v => v.RelativePath = "media.flac")
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(v => v.GetFilesByArtist(1))
|
||||
.Returns(trackFiles);
|
||||
|
||||
GivenSuccessfulScan();
|
||||
|
||||
Subject.Handle(new ArtistScannedEvent(_artist));
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Verify(v => v.GetMediaInfo("media.flac"), Times.Never());
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(v => v.Update(It.IsAny<TrackFile>()), Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_continue_after_failure()
|
||||
{
|
||||
var episodeFiles = Builder<TrackFile>.CreateListOfSize(2)
|
||||
.All()
|
||||
.With(v => v.RelativePath = "media.flac")
|
||||
.TheFirst(1)
|
||||
.With(v => v.RelativePath = "media2.flac")
|
||||
.BuildList();
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(v => v.GetFilesByArtist(1))
|
||||
.Returns(episodeFiles);
|
||||
|
||||
GivenFileExists();
|
||||
GivenSuccessfulScan();
|
||||
GivenFailedScan(Path.Combine(_artist.Path, "media2.flac"));
|
||||
|
||||
Subject.Handle(new ArtistScannedEvent(_artist));
|
||||
|
||||
Mocker.GetMock<IVideoFileInfoReader>()
|
||||
.Verify(v => v.GetMediaInfo(Path.Combine(_artist.Path, "media.flac")), Times.Exactly(1));
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Verify(v => v.Update(It.IsAny<TrackFile>()), Times.Exactly(1));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
using System.IO;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common.Categories;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
|
||||
{
|
||||
[TestFixture]
|
||||
[DiskAccessTest]
|
||||
public class VideoFileInfoReaderFixture : CoreTest<VideoFileInfoReader>
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.FileExists(It.IsAny<string>()))
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(s => s.OpenReadStream(It.IsAny<string>()))
|
||||
.Returns<string>(s => new FileStream(s, FileMode.Open, FileAccess.Read));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_runtime()
|
||||
{
|
||||
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "H264_sample.mp4");
|
||||
|
||||
Subject.GetRunTime(path).Seconds.Should().Be(10);
|
||||
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void get_info()
|
||||
{
|
||||
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "H264_sample.mp4");
|
||||
|
||||
var info = Subject.GetMediaInfo(path);
|
||||
|
||||
info.VideoCodec.Should().BeNull();
|
||||
info.VideoFormat.Should().Be("AVC");
|
||||
info.VideoCodecID.Should().Be("avc1");
|
||||
info.VideoProfile.Should().Be("Baseline@L2.1");
|
||||
info.VideoCodecLibrary.Should().Be("");
|
||||
info.AudioFormat.Should().Be("AAC");
|
||||
info.AudioCodecID.Should().BeOneOf("40", "mp4a-40-2");
|
||||
info.AudioProfile.Should().Be("LC");
|
||||
info.AudioCodecLibrary.Should().Be("");
|
||||
info.AudioBitrate.Should().Be(128000);
|
||||
info.AudioChannels.Should().Be(2);
|
||||
info.AudioLanguages.Should().Be("English");
|
||||
info.Height.Should().Be(320);
|
||||
info.RunTime.Seconds.Should().Be(10);
|
||||
info.ScanType.Should().Be("Progressive");
|
||||
info.Subtitles.Should().Be("");
|
||||
info.VideoBitrate.Should().Be(193329);
|
||||
info.VideoFps.Should().Be(24);
|
||||
info.Width.Should().Be(480);
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_info_unicode()
|
||||
{
|
||||
var srcPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "H264_sample.mp4");
|
||||
|
||||
var tempPath = GetTempFilePath();
|
||||
Directory.CreateDirectory(tempPath);
|
||||
|
||||
var path = Path.Combine(tempPath, "H264_Pok\u00E9mon.mkv");
|
||||
|
||||
File.Copy(srcPath, path);
|
||||
|
||||
var info = Subject.GetMediaInfo(path);
|
||||
|
||||
info.VideoCodec.Should().BeNull();
|
||||
info.VideoFormat.Should().Be("AVC");
|
||||
info.VideoCodecID.Should().Be("avc1");
|
||||
info.VideoProfile.Should().Be("Baseline@L2.1");
|
||||
info.VideoCodecLibrary.Should().Be("");
|
||||
info.AudioFormat.Should().Be("AAC");
|
||||
info.AudioCodecID.Should().BeOneOf("40", "mp4a-40-2");
|
||||
info.AudioProfile.Should().Be("LC");
|
||||
info.AudioCodecLibrary.Should().Be("");
|
||||
info.AudioBitrate.Should().Be(128000);
|
||||
info.AudioChannels.Should().Be(2);
|
||||
info.AudioLanguages.Should().Be("English");
|
||||
info.Height.Should().Be(320);
|
||||
info.RunTime.Seconds.Should().Be(10);
|
||||
info.ScanType.Should().Be("Progressive");
|
||||
info.Subtitles.Should().Be("");
|
||||
info.VideoBitrate.Should().Be(193329);
|
||||
info.VideoFps.Should().Be(24);
|
||||
info.Width.Should().Be(480);
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_dispose_file_after_scanning_mediainfo()
|
||||
{
|
||||
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Media", "H264_sample.mp4");
|
||||
|
||||
var info = Subject.GetMediaInfo(path);
|
||||
|
||||
var stream = new FileStream(path, FileMode.Open, FileAccess.Write);
|
||||
|
||||
stream.Close();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using NzbDrone.Test.Common;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Aggregation.Aggregators;
|
||||
using FluentAssertions;
|
||||
using System.Text;
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Aggregation.Aggregators
|
||||
{
|
||||
[TestFixture]
|
||||
public class AggregateFilenameInfoFixture : CoreTest<AggregateFilenameInfo>
|
||||
{
|
||||
|
||||
private LocalAlbumRelease GivenTracks(List<string> files, string root)
|
||||
{
|
||||
var tracks = files.Select(x => new LocalTrack {
|
||||
Path = Path.Combine(root, x),
|
||||
FileTrackInfo = new ParsedTrackInfo {
|
||||
TrackNumbers = new [] { 0 },
|
||||
}
|
||||
}).ToList();
|
||||
return new LocalAlbumRelease(tracks);
|
||||
}
|
||||
|
||||
private void VerifyData(LocalTrack track, string artist, string title, int trackNum, int disc)
|
||||
{
|
||||
track.FileTrackInfo.ArtistTitle.Should().Be(artist);
|
||||
track.FileTrackInfo.Title.Should().Be(title);
|
||||
track.FileTrackInfo.TrackNumbers[0].Should().Be(trackNum);
|
||||
track.FileTrackInfo.DiscNumber.Should().Be(disc);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_aggregate_filenames_example()
|
||||
{
|
||||
var release = GivenTracks(new List<string> {
|
||||
"Adele - 19 - 101 - Daydreamer.mp3",
|
||||
"Adele - 19 - 102 - Best for Last.mp3",
|
||||
"Adele - 19 - 103 - Chasing Pavements.mp3",
|
||||
"Adele - 19 - 203 - That's It, I Quit, I'm Moving On.mp3"
|
||||
}, @"C:\incoming".AsOsAgnostic());
|
||||
|
||||
Subject.Aggregate(release, true);
|
||||
|
||||
VerifyData(release.LocalTracks[0], "Adele", "Daydreamer", 1, 1);
|
||||
VerifyData(release.LocalTracks[1], "Adele", "Best for Last", 2, 1);
|
||||
VerifyData(release.LocalTracks[2], "Adele", "Chasing Pavements", 3, 1);
|
||||
VerifyData(release.LocalTracks[3], "Adele", "That's It, I Quit, I'm Moving On", 3, 2);
|
||||
}
|
||||
|
||||
public static class TestCaseFactory
|
||||
{
|
||||
private static List<string[]> tokenList = new List<string[]> {
|
||||
|
||||
new [] {"trackNum2", "artist", "title", "tag"},
|
||||
new [] {"trackNum3", "artist", "title", "tag"},
|
||||
new [] {"trackNum2", "artist", "tag", "title"},
|
||||
new [] {"trackNum3", "artist", "tag", "title"},
|
||||
new [] {"trackNum2", "artist", "title"},
|
||||
new [] {"trackNum3", "artist", "title"},
|
||||
|
||||
new [] {"artist", "tag", "trackNum2", "title"},
|
||||
new [] {"artist", "tag", "trackNum3", "title"},
|
||||
new [] {"artist", "trackNum2", "title", "tag"},
|
||||
new [] {"artist", "trackNum3", "title", "tag"},
|
||||
new [] {"artist", "trackNum2", "title"},
|
||||
new [] {"artist", "trackNum3", "title"},
|
||||
|
||||
new [] {"artist", "title", "tag"},
|
||||
new [] {"artist", "tag", "title"},
|
||||
new [] {"artist", "title"},
|
||||
|
||||
new [] {"trackNum2", "title"},
|
||||
new [] {"trackNum3", "title"},
|
||||
|
||||
new [] {"title"},
|
||||
};
|
||||
|
||||
private static List<Tuple<string, string>> separators = new List<Tuple<string, string>> {
|
||||
Tuple.Create(" - ", " "),
|
||||
Tuple.Create("_", " "),
|
||||
Tuple.Create("-", "_")
|
||||
};
|
||||
|
||||
private static List<Tuple<string[], string, string>> otherCases = new List<Tuple<string[], string, string>> {
|
||||
Tuple.Create(new [] {"track2", "title"}, " ", " "),
|
||||
Tuple.Create(new [] {"track3", "title"}, " ", " ")
|
||||
};
|
||||
|
||||
public static IEnumerable TestCases
|
||||
{
|
||||
get
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
foreach (var tokens in tokenList)
|
||||
{
|
||||
foreach (var separator in separators)
|
||||
{
|
||||
i++;
|
||||
yield return new TestCaseData(Tuple.Create(tokens, separator.Item1, separator.Item2))
|
||||
.SetName($"should_aggregate_filenames_auto_{i}")
|
||||
.SetDescription($"tokens: {string.Join(", ", tokens)}, separator: '{separator.Item1}', whitespace: '{separator.Item2}'");
|
||||
}
|
||||
}
|
||||
|
||||
// and a few other cases where all the permutations don't make sense
|
||||
foreach (var item in otherCases)
|
||||
{
|
||||
i++;
|
||||
yield return new TestCaseData(item)
|
||||
.SetName($"should_aggregate_filenames_auto_{i}")
|
||||
.SetDescription($"tokens: {string.Join(", ", item.Item1)}, separator: '{item.Item2}', whitespace: '{item.Item3}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<string> GivenFilenames(string[] fields, string fieldSeparator, string whitespace)
|
||||
{
|
||||
var outp = new List<string>();
|
||||
for (int i = 1; i <= 3; i++)
|
||||
{
|
||||
var components = new List<string>();
|
||||
foreach (var field in fields)
|
||||
{
|
||||
switch(field)
|
||||
{
|
||||
case "artist":
|
||||
components.Add("artist name".Replace(" ", whitespace));
|
||||
break;
|
||||
case "tag":
|
||||
components.Add("tag string ignore".Replace(" ", whitespace));
|
||||
break;
|
||||
case "title":
|
||||
components.Add($"{(char)(96+i)} track title {i}".Replace(" ", whitespace));
|
||||
break;
|
||||
case "trackNum2":
|
||||
components.Add(i.ToString("00"));
|
||||
break;
|
||||
case "trackNum3":
|
||||
components.Add((100 + i).ToString("000"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
outp.Add(string.Join(fieldSeparator, components) + ".mp3");
|
||||
}
|
||||
|
||||
return outp;
|
||||
}
|
||||
|
||||
private void VerifyDataAuto(List<LocalTrack> tracks, string[] tokens, string whitespace)
|
||||
{
|
||||
for (int i = 1; i <= tracks.Count; i++)
|
||||
{
|
||||
var info = tracks[i-1].FileTrackInfo;
|
||||
|
||||
if (tokens.Contains("artist"))
|
||||
{
|
||||
info.ArtistTitle.Should().Be("artist name".Replace(" ", whitespace));
|
||||
}
|
||||
|
||||
if (tokens.Contains("title"))
|
||||
{
|
||||
info.Title.Should().Be($"{(char)(96+i)} track title {i}".Replace(" ", whitespace));
|
||||
}
|
||||
|
||||
if (tokens.Contains("trackNum2") || tokens.Contains("trackNum3"))
|
||||
{
|
||||
info.TrackNumbers[0].Should().Be(i);
|
||||
}
|
||||
|
||||
if (tokens.Contains("trackNum3"))
|
||||
{
|
||||
info.DiscNumber.Should().Be(1);
|
||||
}
|
||||
else
|
||||
{
|
||||
info.DiscNumber.Should().Be(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test, TestCaseSource(typeof(TestCaseFactory), "TestCases")]
|
||||
public void should_aggregate_filenames_auto(Tuple<string[], string, string> testcase)
|
||||
{
|
||||
var files = GivenFilenames(testcase.Item1, testcase.Item2, testcase.Item3);
|
||||
var release = GivenTracks(files, @"C:\incoming".AsOsAgnostic());
|
||||
|
||||
Subject.Aggregate(release, true);
|
||||
|
||||
VerifyDataAuto(release.LocalTracks, testcase.Item1, testcase.Item3);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,216 @@
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
using FluentAssertions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using FizzWare.NBuilder;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Music;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using NzbDrone.Core.Parser;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
[TestFixture]
|
||||
public class AlbumDistanceFixture : CoreTest<IdentificationService>
|
||||
{
|
||||
|
||||
private ArtistMetadata artist;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
artist = Builder<ArtistMetadata>
|
||||
.CreateNew()
|
||||
.With(x => x.Name = "artist")
|
||||
.Build();
|
||||
}
|
||||
|
||||
private List<Track> GivenTracks(int count)
|
||||
{
|
||||
return Builder<Track>
|
||||
.CreateListOfSize(count)
|
||||
.All()
|
||||
.With(x => x.ArtistMetadata = artist)
|
||||
.With(x => x.MediumNumber = 1)
|
||||
.Build()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private LocalTrack GivenLocalTrack(Track track, AlbumRelease release)
|
||||
{
|
||||
var fileInfo = Builder<ParsedTrackInfo>
|
||||
.CreateNew()
|
||||
.With(x => x.Title = track.Title)
|
||||
.With(x => x.CleanTitle = track.Title.CleanTrackTitle())
|
||||
.With(x => x.AlbumTitle = release.Title)
|
||||
.With(x => x.Disambiguation = release.Disambiguation)
|
||||
.With(x => x.ReleaseMBId = release.ForeignReleaseId)
|
||||
.With(x => x.ArtistTitle = track.ArtistMetadata.Value.Name)
|
||||
.With(x => x.TrackNumbers = new[] { track.AbsoluteTrackNumber })
|
||||
.With(x => x.DiscCount = release.Media.Count)
|
||||
.With(x => x.DiscNumber = track.MediumNumber)
|
||||
.With(x => x.RecordingMBId = track.ForeignRecordingId)
|
||||
.With(x => x.Country = IsoCountries.Find("US"))
|
||||
.With(x => x.Label = release.Label.First())
|
||||
.With(x => x.Year = (uint)release.Album.Value.ReleaseDate.Value.Year)
|
||||
.Build();
|
||||
|
||||
var localTrack = Builder<LocalTrack>
|
||||
.CreateNew()
|
||||
.With(x => x.FileTrackInfo = fileInfo)
|
||||
.Build();
|
||||
|
||||
return localTrack;
|
||||
}
|
||||
|
||||
private List<LocalTrack> GivenLocalTracks(List<Track> tracks, AlbumRelease release)
|
||||
{
|
||||
var output = new List<LocalTrack>();
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
output.Add(GivenLocalTrack(track, release));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
private AlbumRelease GivenAlbumRelease(string title, List<Track> tracks)
|
||||
{
|
||||
var album = Builder<Album>
|
||||
.CreateNew()
|
||||
.With(x => x.Title = title)
|
||||
.With(x => x.ArtistMetadata = artist)
|
||||
.Build();
|
||||
|
||||
var media = Builder<Medium>
|
||||
.CreateListOfSize(tracks.Max(x => x.MediumNumber))
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
return Builder<AlbumRelease>
|
||||
.CreateNew()
|
||||
.With(x => x.Tracks = tracks)
|
||||
.With(x => x.Title = title)
|
||||
.With(x => x.Album = album)
|
||||
.With(x => x.Media = media)
|
||||
.With(x => x.Country = new List<string> { "United States" })
|
||||
.With(x => x.Label = new List<string> { "label" })
|
||||
.Build();
|
||||
}
|
||||
|
||||
private TrackMapping GivenMapping(List<LocalTrack> local, List<Track> remote)
|
||||
{
|
||||
var mapping = new TrackMapping();
|
||||
var distances = local.Zip(remote, (l, r) => Tuple.Create(r, Subject.TrackDistance(l, r, Subject.GetTotalTrackNumber(r, remote))));
|
||||
mapping.Mapping = local.Zip(distances, (l, r) => new { l, r }).ToDictionary(x => x.l, x => x.r);
|
||||
mapping.LocalExtra = local.Except(mapping.Mapping.Keys).ToList();
|
||||
mapping.MBExtra = remote.Except(mapping.Mapping.Values.Select(x => x.Item1)).ToList();
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_identical_albums()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
Subject.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_incomplete_album()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
localTracks.RemoveAt(1);
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
var dist = Subject.AlbumReleaseDistance(localTracks, release, mapping);
|
||||
dist.NormalizedDistance().Should().NotBe(0.0);
|
||||
dist.NormalizedDistance().Should().BeLessThan(0.2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_global_artists_differ()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
release.Album.Value.ArtistMetadata = Builder<ArtistMetadata>
|
||||
.CreateNew()
|
||||
.With(x => x.Name = "different artist")
|
||||
.Build();
|
||||
|
||||
Subject.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().NotBe(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_comp_track_artists_match()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
release.Album.Value.ArtistMetadata = Builder<ArtistMetadata>
|
||||
.CreateNew()
|
||||
.With(x => x.Name = "Various Artists")
|
||||
.With(x => x.ForeignArtistId = "89ad4ac3-39f7-470e-963a-56509c546377")
|
||||
.Build();
|
||||
|
||||
Subject.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
|
||||
// TODO: there are a couple more VA tests in beets but we don't support VA yet anyway
|
||||
|
||||
[Test]
|
||||
public void test_tracks_out_of_order()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
localTracks = new [] {1, 3, 2}.Select(x => localTracks[x-1]).ToList();
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
var dist = Subject.AlbumReleaseDistance(localTracks, release, mapping);
|
||||
dist.NormalizedDistance().Should().NotBe(0.0);
|
||||
dist.NormalizedDistance().Should().BeLessThan(0.2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_two_medium_release()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
tracks[2].AbsoluteTrackNumber = 1;
|
||||
tracks[2].MediumNumber = 2;
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
Subject.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_absolute_track_numbering()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
tracks[2].AbsoluteTrackNumber = 1;
|
||||
tracks[2].MediumNumber = 2;
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
localTracks[2].FileTrackInfo.DiscNumber = 2;
|
||||
localTracks[2].FileTrackInfo.TrackNumbers = new[] { 3 };
|
||||
|
||||
var mapping = GivenMapping(localTracks, tracks);
|
||||
|
||||
Subject.AlbumReleaseDistance(localTracks, release, mapping).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,167 @@
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
using NzbDrone.Test.Common;
|
||||
using FluentAssertions;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
[TestFixture]
|
||||
public class DistanceFixture : TestBase
|
||||
{
|
||||
[Test]
|
||||
public void test_add()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.Add("add", 1.0);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"add", new List<double> { 1.0 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_equality()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.AddEquality("equality", "ghi", new List<string> { "abc", "def", "ghi" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"equality", new List<double> { 0.0 }}} );
|
||||
|
||||
dist.AddEquality("equality", "xyz", new List<string> { "abc", "def", "ghi" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"equality", new List<double> { 0.0, 1.0 }}} );
|
||||
|
||||
dist.AddEquality("equality", "abc", new List<string> { "abc", "def", "ghi" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"equality", new List<double> { 0.0, 1.0, 0.0 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_add_bool()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.AddBool("expr", true);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"expr", new List<double> { 1.0 }}} );
|
||||
|
||||
dist.AddBool("expr", false);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"expr", new List<double> { 1.0, 0.0 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_add_number()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.AddNumber("number", 1, 1);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"number", new List<double> { 0.0 }}} );
|
||||
|
||||
dist.AddNumber("number", 1, 2);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"number", new List<double> { 0.0, 1.0 }}} );
|
||||
|
||||
dist.AddNumber("number", 2, 1);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"number", new List<double> { 0.0, 1.0, 1.0 }}} );
|
||||
|
||||
dist.AddNumber("number", -1, 2);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"number", new List<double> { 0.0, 1.0, 1.0, 1.0, 1.0, 1.0 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_add_priority_value()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.AddPriority("priority", "abc", new List<string> { "abc" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"priority", new List<double> { 0.0 }}} );
|
||||
|
||||
dist.AddPriority("priority", "def", new List<string> { "abc", "def" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"priority", new List<double> { 0.0, 0.5 }}} );
|
||||
|
||||
dist.AddPriority("priority", "xyz", new List<string> { "abc", "def" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"priority", new List<double> { 0.0, 0.5, 1.0 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_add_priority_list()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.AddPriority("priority", new List<string> { "abc" }, new List<string> { "abc" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"priority", new List<double> { 0.0 }}} );
|
||||
|
||||
dist.AddPriority("priority", new List<string> { "def" }, new List<string> { "abc" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"priority", new List<double> { 0.0, 1.0 }}} );
|
||||
|
||||
dist.AddPriority("priority", new List<string> { "abc", "xyz" }, new List<string> { "abc" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"priority", new List<double> { 0.0, 1.0, 0.0 }}} );
|
||||
|
||||
dist.AddPriority("priority", new List<string> { "def", "xyz" }, new List<string> { "abc", "def" });
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"priority", new List<double> { 0.0, 1.0, 0.0, 0.5 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_add_ratio()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.AddRatio("ratio", 25, 100);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"ratio", new List<double> { 0.25 }}} );
|
||||
|
||||
dist.AddRatio("ratio", 10, 5);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"ratio", new List<double> { 0.25, 1.0 }}} );
|
||||
|
||||
dist.AddRatio("ratio", -5, 5);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"ratio", new List<double> { 0.25, 1.0, 0.0 }}} );
|
||||
|
||||
dist.AddRatio("ratio", 5, 0);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"ratio", new List<double> { 0.25, 1.0, 0.0, 0.0 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_add_string()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.AddString("string", "abcd", "bcde");
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"string", new List<double> { 0.5 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_add_string_none()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.AddString("string", string.Empty, "bcd");
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"string", new List<double> { 1.0 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_add_string_both_none()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.AddString("string", string.Empty, string.Empty);
|
||||
dist.Penalties.ShouldBeEquivalentTo(new Dictionary<string, List<double>> { {"string", new List<double> { 0.0 }}} );
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_distance()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.Add("album", 0.5);
|
||||
dist.Add("media_count", 0.25);
|
||||
dist.Add("media_count", 0.75);
|
||||
|
||||
dist.NormalizedDistance().Should().Be(0.5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_max_distance()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.Add("album", 0.5);
|
||||
dist.Add("media_count", 0.0);
|
||||
dist.Add("media_count", 0.0);
|
||||
|
||||
dist.MaxDistance().Should().Be(5.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_raw_distance()
|
||||
{
|
||||
var dist = new Distance();
|
||||
dist.Add("album", 0.5);
|
||||
dist.Add("media_count", 0.25);
|
||||
dist.Add("media_count", 0.5);
|
||||
|
||||
dist.RawDistance().Should().Be(2.25);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
using FluentAssertions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using FizzWare.NBuilder;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Music;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using Moq;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
[TestFixture]
|
||||
public class GetCandidatesFixture : CoreTest<IdentificationService>
|
||||
{
|
||||
|
||||
private ArtistMetadata artist;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
artist = Builder<ArtistMetadata>
|
||||
.CreateNew()
|
||||
.With(x => x.Name = "artist")
|
||||
.Build();
|
||||
}
|
||||
|
||||
private List<Track> GivenTracks(int count)
|
||||
{
|
||||
return Builder<Track>
|
||||
.CreateListOfSize(count)
|
||||
.All()
|
||||
.With(x => x.ArtistMetadata = artist)
|
||||
.Build()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private ParsedTrackInfo GivenParsedTrackInfo(Track track, AlbumRelease release)
|
||||
{
|
||||
return Builder<ParsedTrackInfo>
|
||||
.CreateNew()
|
||||
.With(x => x.Title = track.Title)
|
||||
.With(x => x.AlbumTitle = release.Title)
|
||||
.With(x => x.Disambiguation = release.Disambiguation)
|
||||
.With(x => x.ReleaseMBId = release.ForeignReleaseId)
|
||||
.With(x => x.ArtistTitle = track.ArtistMetadata.Value.Name)
|
||||
.With(x => x.TrackNumbers = new[] { track.AbsoluteTrackNumber })
|
||||
.With(x => x.RecordingMBId = track.ForeignRecordingId)
|
||||
.With(x => x.Country = IsoCountries.Find("US"))
|
||||
.With(x => x.Label = release.Label.First())
|
||||
.With(x => x.Year = (uint)release.Album.Value.ReleaseDate.Value.Year)
|
||||
.Build();
|
||||
}
|
||||
|
||||
private List<LocalTrack> GivenLocalTracks(List<Track> tracks, AlbumRelease release)
|
||||
{
|
||||
var output = Builder<LocalTrack>
|
||||
.CreateListOfSize(tracks.Count)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
for (int i = 0; i < tracks.Count; i++)
|
||||
{
|
||||
output[i].FileTrackInfo = GivenParsedTrackInfo(tracks[i], release);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private AlbumRelease GivenAlbumRelease(string title, List<Track> tracks)
|
||||
{
|
||||
var album = Builder<Album>
|
||||
.CreateNew()
|
||||
.With(x => x.Title = title)
|
||||
.With(x => x.ArtistMetadata = artist)
|
||||
.Build();
|
||||
|
||||
var media = Builder<Medium>
|
||||
.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
return Builder<AlbumRelease>
|
||||
.CreateNew()
|
||||
.With(x => x.Tracks = tracks)
|
||||
.With(x => x.Title = title)
|
||||
.With(x => x.Album = album)
|
||||
.With(x => x.Media = media)
|
||||
.With(x => x.Country = new List<string>())
|
||||
.With(x => x.Label = new List<string> { "label" })
|
||||
.With(x => x.ForeignReleaseId = null)
|
||||
.Build();
|
||||
}
|
||||
|
||||
private LocalAlbumRelease GivenLocalAlbumRelease()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
|
||||
return new LocalAlbumRelease(localTracks);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_candidates_by_fingerprint_should_not_fail_if_fingerprint_lookup_returned_null()
|
||||
{
|
||||
Mocker.GetMock<IFingerprintingService>()
|
||||
.Setup(x => x.Lookup(It.IsAny<List<LocalTrack>>(), It.IsAny<double>()))
|
||||
.Callback((List<LocalTrack> x, double thres) => {
|
||||
foreach(var track in x) {
|
||||
track.AcoustIdResults = null;
|
||||
}
|
||||
});
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Setup(x => x.GetReleasesByRecordingIds(It.IsAny<List<string>>()))
|
||||
.Returns(new List<AlbumRelease>());
|
||||
|
||||
var local = GivenLocalAlbumRelease();
|
||||
|
||||
Subject.GetCandidatesFromFingerprint(local).ShouldBeEquivalentTo(new List<AlbumRelease>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_candidates_should_only_return_specified_release_if_set()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
var localAlbumRelease = new LocalAlbumRelease(localTracks);
|
||||
|
||||
Subject.GetCandidatesFromTags(localAlbumRelease, null, null, release).ShouldBeEquivalentTo(new List<AlbumRelease> { release });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void get_candidates_should_use_consensus_release_id()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
release.ForeignReleaseId = "xxx";
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
var localAlbumRelease = new LocalAlbumRelease(localTracks);
|
||||
|
||||
Mocker.GetMock<IReleaseService>()
|
||||
.Setup(x => x.GetReleasesByForeignReleaseId(new List<string>{ "xxx" }))
|
||||
.Returns(new List<AlbumRelease> { release });
|
||||
|
||||
Subject.GetCandidatesFromTags(localAlbumRelease, null, null, null).ShouldBeEquivalentTo(new List<AlbumRelease> { release });
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,140 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using FluentAssertions;
|
||||
using FluentValidation.Results;
|
||||
using Moq;
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.MetadataSource;
|
||||
using NzbDrone.Core.MetadataSource.SkyHook;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Music.Commands;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Profiles.Metadata;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
[TestFixture]
|
||||
public class IdentificationServiceFixture : DbTest
|
||||
{
|
||||
private ArtistService _artistService;
|
||||
private AddArtistService _addArtistService;
|
||||
private RefreshArtistService _refreshArtistService;
|
||||
|
||||
private IdentificationService Subject;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
UseRealHttp();
|
||||
|
||||
// Resolve all the parts we need
|
||||
Mocker.SetConstant<IArtistRepository>(Mocker.Resolve<ArtistRepository>());
|
||||
Mocker.SetConstant<IArtistMetadataRepository>(Mocker.Resolve<ArtistMetadataRepository>());
|
||||
Mocker.SetConstant<IAlbumRepository>(Mocker.Resolve<AlbumRepository>());
|
||||
Mocker.SetConstant<IReleaseRepository>(Mocker.Resolve<ReleaseRepository>());
|
||||
Mocker.SetConstant<ITrackRepository>(Mocker.Resolve<TrackRepository>());
|
||||
|
||||
Mocker.GetMock<IMetadataProfileService>().Setup(x => x.Exists(It.IsAny<int>())).Returns(true);
|
||||
|
||||
_artistService = Mocker.Resolve<ArtistService>();
|
||||
Mocker.SetConstant<IArtistService>(_artistService);
|
||||
Mocker.SetConstant<IAlbumService>(Mocker.Resolve<AlbumService>());
|
||||
Mocker.SetConstant<IReleaseService>(Mocker.Resolve<ReleaseService>());
|
||||
Mocker.SetConstant<ITrackService>(Mocker.Resolve<TrackService>());
|
||||
|
||||
Mocker.SetConstant<IConfigService>(Mocker.Resolve<IConfigService>());
|
||||
Mocker.SetConstant<IProvideArtistInfo>(Mocker.Resolve<SkyHookProxy>());
|
||||
Mocker.SetConstant<IProvideAlbumInfo>(Mocker.Resolve<SkyHookProxy>());
|
||||
|
||||
_addArtistService = Mocker.Resolve<AddArtistService>();
|
||||
|
||||
Mocker.SetConstant<IRefreshTrackService>(Mocker.Resolve<RefreshTrackService>());
|
||||
Mocker.SetConstant<IAddAlbumService>(Mocker.Resolve<AddAlbumService>());
|
||||
_refreshArtistService = Mocker.Resolve<RefreshArtistService>();
|
||||
|
||||
Mocker.GetMock<IAddArtistValidator>().Setup(x => x.Validate(It.IsAny<Artist>())).Returns(new ValidationResult());
|
||||
|
||||
Mocker.SetConstant<ITrackGroupingService>(Mocker.Resolve<TrackGroupingService>());
|
||||
Subject = Mocker.Resolve<IdentificationService>();
|
||||
|
||||
}
|
||||
|
||||
private void GivenMetadataProfile(MetadataProfile profile)
|
||||
{
|
||||
Mocker.GetMock<IMetadataProfileService>().Setup(x => x.Get(It.IsAny<int>())).Returns(profile);
|
||||
}
|
||||
|
||||
private Artist GivenArtist(string foreignArtistId)
|
||||
{
|
||||
var artist = _addArtistService.AddArtist(new Artist {
|
||||
Metadata = new ArtistMetadata {
|
||||
ForeignArtistId = foreignArtistId
|
||||
},
|
||||
Path = @"c:\test".AsOsAgnostic(),
|
||||
MetadataProfileId = 1
|
||||
});
|
||||
|
||||
var command = new RefreshArtistCommand{
|
||||
ArtistId = artist.Id,
|
||||
Trigger = CommandTrigger.Unspecified
|
||||
};
|
||||
|
||||
_refreshArtistService.Execute(command);
|
||||
|
||||
return _artistService.FindById(foreignArtistId);
|
||||
}
|
||||
|
||||
public static class IdTestCaseFactory
|
||||
{
|
||||
// for some reason using Directory.GetFiles causes nUnit to error
|
||||
private static string[] files = {
|
||||
"FilesWithMBIds.json",
|
||||
"PreferMissingToBadMatch.json",
|
||||
"InconsistentTyposInAlbum.json",
|
||||
"SucceedWhenManyAlbumsHaveSameTitle.json",
|
||||
"PenalizeUnknownMedia.json"
|
||||
};
|
||||
|
||||
public static IEnumerable TestCases
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
yield return new TestCaseData(file).SetName($"should_match_tracks_{file.Replace(".json", "")}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// these are slow to run so only do so manually
|
||||
[Explicit]
|
||||
[Test, TestCaseSource(typeof(IdTestCaseFactory), "TestCases")]
|
||||
public void should_match_tracks(string file)
|
||||
{
|
||||
var path = Path.Combine(TestContext.CurrentContext.TestDirectory, "Files", "Identification", file);
|
||||
var testcase = JsonConvert.DeserializeObject<IdTestCase>(File.ReadAllText(path));
|
||||
|
||||
GivenMetadataProfile(testcase.MetadataProfile);
|
||||
|
||||
var artist = GivenArtist(testcase.Artist);
|
||||
|
||||
var tracks = testcase.Tracks.Select(x => new LocalTrack {
|
||||
Path = x.Path.AsOsAgnostic(),
|
||||
FileTrackInfo = x.FileTrackInfo
|
||||
}).ToList();
|
||||
|
||||
var result = Subject.Identify(tracks, artist, null, null, testcase.NewDownload, testcase.SingleRelease);
|
||||
|
||||
result.Should().HaveCount(testcase.ExpectedMusicBrainzReleaseIds.Count);
|
||||
result.Select(x => x.AlbumRelease.ForeignReleaseId).ShouldBeEquivalentTo(testcase.ExpectedMusicBrainzReleaseIds);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
using NzbDrone.Test.Common;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
[TestFixture]
|
||||
public class MunkresFixture : TestBase
|
||||
{
|
||||
// 2d arrays don't play nicely with attributes
|
||||
public void RunTest(double[,] costMatrix, double expectedCost)
|
||||
{
|
||||
var m = new Munkres(costMatrix);
|
||||
m.Run();
|
||||
m.Cost.Should().Be(expectedCost);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MunkresSquareTest1()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{ 1, 2, 3 },
|
||||
{ 2, 4, 6 },
|
||||
{ 3, 6, 9 }
|
||||
};
|
||||
|
||||
RunTest(C, 10);
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MunkresSquareTest2()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{ 400, 150, 400 },
|
||||
{ 400, 450, 600 },
|
||||
{ 300, 225, 300 }
|
||||
};
|
||||
|
||||
RunTest(C, 850);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MunkresSquareTest3()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{ 10, 10, 8 },
|
||||
{ 9, 8, 1 },
|
||||
{ 9, 7, 4 }
|
||||
};
|
||||
|
||||
RunTest(C, 18);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MunkresSquareTest4()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{ 5, 9, 1 },
|
||||
{ 10, 3, 2 },
|
||||
{ 8, 7, 4 }
|
||||
};
|
||||
|
||||
RunTest(C, 12);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MunkresSquareTest5()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{12, 26, 17, 0, 0},
|
||||
{49, 43, 36, 10, 5},
|
||||
{97, 9, 66, 34, 0},
|
||||
{52, 42, 19, 36, 0},
|
||||
{15, 93, 55, 80, 0}
|
||||
};
|
||||
|
||||
RunTest(C, 48);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Munkres5x5Test()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{12, 9, 27, 10, 23},
|
||||
{7, 13, 13, 30, 19},
|
||||
{25, 18, 26, 11, 26},
|
||||
{9, 28, 26, 23, 13},
|
||||
{16, 16, 24, 6, 9}
|
||||
};
|
||||
|
||||
RunTest(C, 51);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Munkres10x10Test()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{37, 34, 29, 26, 19, 8, 9, 23, 19, 29},
|
||||
{9, 28, 20, 8, 18, 20, 14, 33, 23, 14},
|
||||
{15, 26, 12, 28, 6, 17, 9, 13, 21, 7},
|
||||
{2, 8, 38, 36, 39, 5, 36, 2, 38, 27},
|
||||
{30, 3, 33, 16, 21, 39, 7, 23, 28, 36},
|
||||
{7, 5, 19, 22, 36, 36, 24, 19, 30, 2},
|
||||
{34, 20, 13, 36, 12, 33, 9, 10, 23, 5},
|
||||
{7, 37, 22, 39, 33, 39, 10, 3, 13, 26},
|
||||
{21, 25, 23, 39, 31, 37, 32, 33, 38, 1},
|
||||
{17, 34, 40, 10, 29, 37, 40, 3, 25, 3}
|
||||
};
|
||||
|
||||
RunTest(C, 66);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Munkres20x20Test()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{5, 4, 3, 9, 8, 9, 3, 5, 6, 9, 4, 10, 3, 5, 6, 6, 1, 8, 10, 2},
|
||||
{10, 9, 9, 2, 8, 3, 9, 9, 10, 1, 7, 10, 8, 4, 2, 1, 4, 8, 4, 8},
|
||||
{10, 4, 4, 3, 1, 3, 5, 10, 6, 8, 6, 8, 4, 10, 7, 2, 4, 5, 1, 8},
|
||||
{2, 1, 4, 2, 3, 9, 3, 4, 7, 3, 4, 1, 3, 2, 9, 8, 6, 5, 7, 8},
|
||||
{3, 4, 4, 1, 4, 10, 1, 2, 6, 4, 5, 10, 2, 2, 3, 9, 10, 9, 9, 10},
|
||||
{1, 10, 1, 8, 1, 3, 1, 7, 1, 1, 2, 1, 2, 6, 3, 3, 4, 4, 8, 6},
|
||||
{1, 8, 7, 10, 10, 3, 4, 6, 1, 6, 6, 4, 9, 6, 9, 6, 4, 5, 4, 7},
|
||||
{8, 10, 3, 9, 4, 9, 3, 3, 4, 6, 4, 2, 6, 7, 7, 4, 4, 3, 4, 7},
|
||||
{1, 3, 8, 2, 6, 9, 2, 7, 4, 8, 10, 8, 10, 5, 1, 3, 10, 10, 2, 9},
|
||||
{2, 4, 1, 9, 2, 9, 7, 8, 2, 1, 4, 10, 5, 2, 7, 6, 5, 7, 2, 6},
|
||||
{4, 5, 1, 4, 2, 3, 3, 4, 1, 8, 8, 2, 6, 9, 5, 9, 6, 3, 9, 3},
|
||||
{3, 1, 1, 8, 6, 8, 8, 7, 9, 3, 2, 1, 8, 2, 4, 7, 3, 1, 2, 4},
|
||||
{5, 9, 8, 6, 10, 4, 10, 3, 4, 10, 10, 10, 1, 7, 8, 8, 7, 7, 8, 8},
|
||||
{1, 4, 6, 1, 6, 1, 2, 10, 5, 10, 2, 6, 2, 4, 5, 5, 3, 5, 1, 5},
|
||||
{5, 6, 9, 10, 6, 6, 10, 6, 4, 1, 5, 3, 9, 5, 2, 10, 9, 9, 5, 1},
|
||||
{10, 9, 4, 6, 9, 5, 3, 7, 10, 1, 6, 8, 1, 1, 10, 9, 5, 7, 7, 5},
|
||||
{2, 6, 6, 6, 6, 2, 9, 4, 7, 5, 3, 2, 10, 3, 4, 5, 10, 9, 1, 7},
|
||||
{5, 2, 4, 9, 8, 4, 8, 2, 4, 1, 3, 7, 6, 8, 1, 6, 8, 8, 10, 10},
|
||||
{9, 6, 3, 1, 8, 5, 7, 8, 7, 2, 1, 8, 2, 8, 3, 7, 4, 8, 7, 7},
|
||||
{8, 4, 4, 9, 7, 10, 6, 2, 1, 5, 8, 5, 1, 1, 1, 9, 1, 3, 5, 3}
|
||||
};
|
||||
|
||||
RunTest(C, 22);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MunkresRectangularTest1()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{ 400, 150, 400, 1 },
|
||||
{ 400, 450, 600, 2 },
|
||||
{ 300, 225, 300, 3 }
|
||||
};
|
||||
|
||||
RunTest(C, 452);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MunkresRectangularTest2()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{ 10, 10, 8, 11 },
|
||||
{ 9, 8, 1, 1 },
|
||||
{ 9, 7, 4, 10 }
|
||||
};
|
||||
|
||||
RunTest(C, 15);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MunkresRectangularTest3()
|
||||
{
|
||||
var C = new double[,] {
|
||||
{34, 26, 17, 12},
|
||||
{43, 43, 36, 10},
|
||||
{97, 47, 66, 34},
|
||||
{52, 42, 19, 36},
|
||||
{15, 93, 55, 80}
|
||||
};
|
||||
|
||||
RunTest(C, 70);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,99 @@
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
using FluentAssertions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using FizzWare.NBuilder;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Parser;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
[TestFixture]
|
||||
public class TrackDistanceFixture : CoreTest<IdentificationService>
|
||||
{
|
||||
private Track GivenTrack(string title)
|
||||
{
|
||||
var artist = Builder<ArtistMetadata>
|
||||
.CreateNew()
|
||||
.With(x => x.Name = "artist")
|
||||
.Build();
|
||||
|
||||
var mbTrack = Builder<Track>
|
||||
.CreateNew()
|
||||
.With(x => x.Title = title)
|
||||
.With(x => x.ArtistMetadata = artist)
|
||||
.Build();
|
||||
|
||||
return mbTrack;
|
||||
}
|
||||
|
||||
private LocalTrack GivenLocalTrack(Track track)
|
||||
{
|
||||
var fileInfo = Builder<ParsedTrackInfo>
|
||||
.CreateNew()
|
||||
.With(x => x.Title = track.Title)
|
||||
.With(x => x.CleanTitle = track.Title.CleanTrackTitle())
|
||||
.With(x => x.ArtistTitle = track.ArtistMetadata.Value.Name)
|
||||
.With(x => x.TrackNumbers = new[] { 1 })
|
||||
.With(x => x.RecordingMBId = track.ForeignRecordingId)
|
||||
.Build();
|
||||
|
||||
var localTrack = Builder<LocalTrack>
|
||||
.CreateNew()
|
||||
.With(x => x.FileTrackInfo = fileInfo)
|
||||
.Build();
|
||||
|
||||
return localTrack;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_identical_tracks()
|
||||
{
|
||||
var track = GivenTrack("one");
|
||||
var localTrack = GivenLocalTrack(track);
|
||||
|
||||
Subject.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_feat_removed_from_localtrack()
|
||||
{
|
||||
var track = GivenTrack("one");
|
||||
var localTrack = GivenLocalTrack(track);
|
||||
localTrack.FileTrackInfo.Title = "one (feat. two)";
|
||||
|
||||
Subject.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_different_title()
|
||||
{
|
||||
var track = GivenTrack("one");
|
||||
var localTrack = GivenLocalTrack(track);
|
||||
localTrack.FileTrackInfo.CleanTitle = "foo";
|
||||
|
||||
Subject.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().NotBe(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_different_artist()
|
||||
{
|
||||
var track = GivenTrack("one");
|
||||
var localTrack = GivenLocalTrack(track);
|
||||
localTrack.FileTrackInfo.ArtistTitle = "foo";
|
||||
|
||||
Subject.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().NotBe(0.0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_various_artists_tolerated()
|
||||
{
|
||||
var track = GivenTrack("one");
|
||||
var localTrack = GivenLocalTrack(track);
|
||||
localTrack.FileTrackInfo.ArtistTitle = "Various Artists";
|
||||
|
||||
Subject.TrackDistance(localTrack, track, 1, true).NormalizedDistance().Should().Be(0.0);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,369 @@
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
using NzbDrone.Test.Common;
|
||||
using FluentAssertions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using FizzWare.NBuilder;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using FizzWare.NBuilder.PropertyNaming;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
// we need to use random strings to test the va (so we don't just get artist1, artist2 etc which are too similar)
|
||||
// but the standard random value namer would give paths that are too long on windows
|
||||
public class RandomValueNamerShortStrings : RandomValuePropertyNamer
|
||||
{
|
||||
private readonly IRandomGenerator generator;
|
||||
private static readonly List<char> allowedChars;
|
||||
|
||||
public RandomValueNamerShortStrings(BuilderSettings settings) : base(settings)
|
||||
{
|
||||
generator = new RandomGenerator();
|
||||
}
|
||||
|
||||
static RandomValueNamerShortStrings()
|
||||
{
|
||||
allowedChars = new List<char>();
|
||||
for (char c = 'a'; c < 'z'; c++)
|
||||
{
|
||||
allowedChars.Add(c);
|
||||
}
|
||||
|
||||
for (char c = 'A'; c < 'Z'; c++)
|
||||
{
|
||||
allowedChars.Add(c);
|
||||
}
|
||||
|
||||
for (char c = '0'; c < '9'; c++)
|
||||
{
|
||||
allowedChars.Add(c);
|
||||
}
|
||||
}
|
||||
|
||||
protected override string GetString(MemberInfo memberInfo)
|
||||
{
|
||||
int length = generator.Next(0, 100);
|
||||
|
||||
char[] chars = new char[length];
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
int index = generator.Next(0, allowedChars.Count - 1);
|
||||
chars[i] = allowedChars[index];
|
||||
}
|
||||
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(chars);
|
||||
return Encoding.UTF8.GetString(bytes, 0, bytes.Length);
|
||||
}
|
||||
}
|
||||
|
||||
[TestFixture]
|
||||
public class TrackGroupingServiceFixture : CoreTest<TrackGroupingService>
|
||||
{
|
||||
private List<LocalTrack> GivenTracks(string root, string artist, string album, int count)
|
||||
{
|
||||
var fileInfos = Builder<ParsedTrackInfo>
|
||||
.CreateListOfSize(count)
|
||||
.All()
|
||||
.With(f => f.ArtistTitle = artist)
|
||||
.With(f => f.AlbumTitle = album)
|
||||
.With(f => f.AlbumMBId = null)
|
||||
.With(f => f.ReleaseMBId = null)
|
||||
.Build();
|
||||
|
||||
var tracks = fileInfos.Select(x => Builder<LocalTrack>
|
||||
.CreateNew()
|
||||
.With(y => y.FileTrackInfo = x)
|
||||
.With(y => y.Path = Path.Combine(root, x.Title))
|
||||
.Build()).ToList();
|
||||
|
||||
return tracks;
|
||||
}
|
||||
|
||||
private List<LocalTrack> GivenTracksWithNoTags(string root, int count)
|
||||
{
|
||||
var outp = new List<LocalTrack>();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var track = Builder<LocalTrack>
|
||||
.CreateNew()
|
||||
.With(y => y.FileTrackInfo = new ParsedTrackInfo())
|
||||
.With(y => y.Path = Path.Combine(root, $"{i}.mp3"))
|
||||
.Build();
|
||||
outp.Add(track);
|
||||
}
|
||||
|
||||
return outp;
|
||||
}
|
||||
|
||||
private List<LocalTrack> GivenVaTracks(string root, string album, int count)
|
||||
{
|
||||
var settings = new BuilderSettings();
|
||||
settings.SetPropertyNamerFor<ParsedTrackInfo>(new RandomValueNamerShortStrings(settings));
|
||||
|
||||
var builder = new Builder(settings);
|
||||
|
||||
var fileInfos = builder
|
||||
.CreateListOfSize<ParsedTrackInfo>(count)
|
||||
.All()
|
||||
.With(f => f.AlbumTitle = "album")
|
||||
.With(f => f.AlbumMBId = null)
|
||||
.With(f => f.ReleaseMBId = null)
|
||||
.Build();
|
||||
|
||||
var tracks = fileInfos.Select(x => Builder<LocalTrack>
|
||||
.CreateNew()
|
||||
.With(y => y.FileTrackInfo = x)
|
||||
.With(y => y.Path = Path.Combine(@"C:\music\incoming".AsOsAgnostic(), x.Title))
|
||||
.Build()).ToList();
|
||||
|
||||
return tracks;
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(10)]
|
||||
public void single_artist_is_not_various_artists(int count)
|
||||
{
|
||||
var tracks = GivenTracks(@"C:\music\incoming".AsOsAgnostic(), "artist", "album", count);
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void all_different_artists_is_various_artists()
|
||||
{
|
||||
var tracks = GivenVaTracks(@"C:\music\incoming".AsOsAgnostic(), "album", 10);
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void two_artists_is_not_various_artists()
|
||||
{
|
||||
var dir = @"C:\music\incoming".AsOsAgnostic();
|
||||
var tracks = GivenTracks(dir, "artist1", "album", 10);
|
||||
tracks.AddRange(GivenTracks(dir, "artist2", "album", 10));
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void mostly_different_artists_is_various_artists()
|
||||
{
|
||||
var dir = @"C:\music\incoming".AsOsAgnostic();
|
||||
var tracks = GivenVaTracks(dir, "album", 10);
|
||||
tracks.AddRange(GivenTracks(dir, "single_artist", "album", 2));
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(true);
|
||||
}
|
||||
|
||||
[TestCase("")]
|
||||
[TestCase("Various Artists")]
|
||||
[TestCase("Various")]
|
||||
[TestCase("VA")]
|
||||
[TestCase("Unknown")]
|
||||
public void va_artist_title_is_various_artists(string artist)
|
||||
{
|
||||
var tracks = GivenTracks(@"C:\music\incoming".AsOsAgnostic(), artist, "album", 10);
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(true);
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(2)]
|
||||
[TestCase(10)]
|
||||
public void should_group_single_artist_album(int count)
|
||||
{
|
||||
var tracks = GivenTracks(@"C:\music\incoming".AsOsAgnostic(), "artist", "album", count);
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(true);
|
||||
|
||||
output.Count.Should().Be(1);
|
||||
output[0].LocalTracks.Count.Should().Be(count);
|
||||
}
|
||||
|
||||
[TestCase("cd")]
|
||||
[TestCase("disc")]
|
||||
[TestCase("disk")]
|
||||
public void should_group_multi_disc_release(string mediaName)
|
||||
{
|
||||
var tracks = GivenTracks($"C:\\music\\incoming\\artist - album\\{mediaName} 1".AsOsAgnostic(),
|
||||
"artist", "album", 10);
|
||||
tracks.AddRange(GivenTracks($"C:\\music\\incoming\\artist - album\\{mediaName} 2".AsOsAgnostic(),
|
||||
"artist", "album", 5));
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(true);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(1);
|
||||
output[0].LocalTracks.Count.Should().Be(15);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_group_two_different_albums_by_same_artist()
|
||||
{
|
||||
var tracks = GivenTracks($"C:\\music\\incoming\\artist - album1".AsOsAgnostic(),
|
||||
"artist", "album1", 10);
|
||||
tracks.AddRange(GivenTracks($"C:\\music\\incoming\\artist - album2".AsOsAgnostic(),
|
||||
"artist", "album2", 5));
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(false);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(2);
|
||||
output[0].LocalTracks.Count.Should().Be(10);
|
||||
output[1].LocalTracks.Count.Should().Be(5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_group_albums_with_typos()
|
||||
{
|
||||
var tracks = GivenTracks($"C:\\music\\incoming\\artist - album".AsOsAgnostic(),
|
||||
"artist", "Rastaman Vibration (Remastered)", 10);
|
||||
tracks.AddRange(GivenTracks($"C:\\music\\incoming\\artist - album".AsOsAgnostic(),
|
||||
"artist", "Rastaman Vibration (Remastered", 5));
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(true);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(1);
|
||||
output[0].LocalTracks.Count.Should().Be(15);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_group_two_different_tracks_in_same_directory()
|
||||
{
|
||||
var tracks = GivenTracks($"C:\\music\\incoming".AsOsAgnostic(),
|
||||
"artist", "album1", 1);
|
||||
tracks.AddRange(GivenTracks($"C:\\music\\incoming".AsOsAgnostic(),
|
||||
"artist", "album2", 1));
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(false);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(2);
|
||||
output[0].LocalTracks.Count.Should().Be(1);
|
||||
output[1].LocalTracks.Count.Should().Be(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_separate_two_albums_in_same_directory()
|
||||
{
|
||||
var tracks = GivenTracks($"C:\\music\\incoming\\artist discog".AsOsAgnostic(),
|
||||
"artist", "album1", 10);
|
||||
tracks.AddRange(GivenTracks($"C:\\music\\incoming\\artist disog".AsOsAgnostic(),
|
||||
"artist", "album2", 5));
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(false);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(2);
|
||||
output[0].LocalTracks.Count.Should().Be(10);
|
||||
output[1].LocalTracks.Count.Should().Be(5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_separate_many_albums_in_same_directory()
|
||||
{
|
||||
var tracks = new List<LocalTrack>();
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
tracks.AddRange(GivenTracks($"C:\\music".AsOsAgnostic(),
|
||||
"artist" + i, "album" + i, 10));
|
||||
}
|
||||
|
||||
// don't test various artists here because it's designed to only work if there's a common album
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(false);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(100);
|
||||
output.Select(x => x.LocalTracks.Count).Distinct().ShouldBeEquivalentTo(new List<int> { 10 });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_separate_two_albums_by_different_artists_in_same_directory()
|
||||
{
|
||||
var tracks = GivenTracks($"C:\\music\\incoming".AsOsAgnostic(),
|
||||
"artist1", "album1", 10);
|
||||
tracks.AddRange(GivenTracks($"C:\\music\\incoming".AsOsAgnostic(),
|
||||
"artist2", "album2", 5));
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(false);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(2);
|
||||
output[0].LocalTracks.Count.Should().Be(10);
|
||||
output[1].LocalTracks.Count.Should().Be(5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_group_va_release()
|
||||
{
|
||||
var tracks = GivenVaTracks(@"C:\music\incoming".AsOsAgnostic(), "album", 10);
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(true);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(true);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(1);
|
||||
output[0].LocalTracks.Count.Should().Be(10);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_group_two_albums_by_different_artists_with_same_title()
|
||||
{
|
||||
var tracks = GivenTracks($"C:\\music\\incoming\\album".AsOsAgnostic(),
|
||||
"artist1", "album", 10);
|
||||
tracks.AddRange(GivenTracks($"C:\\music\\incoming\\album".AsOsAgnostic(),
|
||||
"artist2", "album", 5));
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(false);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
|
||||
output.Count.Should().Be(2);
|
||||
output[0].LocalTracks.Count.Should().Be(10);
|
||||
output[1].LocalTracks.Count.Should().Be(5);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_fail_if_all_tags_null()
|
||||
{
|
||||
var tracks = GivenTracksWithNoTags($"C:\\music\\incoming\\album".AsOsAgnostic(), 10);
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(true);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(1);
|
||||
output[0].LocalTracks.Count.Should().Be(10);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_fail_if_some_tags_null()
|
||||
{
|
||||
var tracks = GivenTracks($"C:\\music\\incoming\\album".AsOsAgnostic(),
|
||||
"artist1", "album", 10);
|
||||
tracks.AddRange(GivenTracksWithNoTags($"C:\\music\\incoming\\album".AsOsAgnostic(), 2));
|
||||
|
||||
TrackGroupingService.IsVariousArtists(tracks).Should().Be(false);
|
||||
TrackGroupingService.LooksLikeSingleRelease(tracks).Should().Be(true);
|
||||
|
||||
var output = Subject.GroupTracks(tracks);
|
||||
output.Count.Should().Be(1);
|
||||
output[0].LocalTracks.Count.Should().Be(12);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,187 @@
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles.TrackImport.Identification;
|
||||
using FluentAssertions;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using FizzWare.NBuilder;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Music;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Common.Serializer;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.TrackImport.Identification
|
||||
{
|
||||
[TestFixture]
|
||||
public class TrackMappingFixture : CoreTest<IdentificationService>
|
||||
{
|
||||
|
||||
private ArtistMetadata artist;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
artist = Builder<ArtistMetadata>
|
||||
.CreateNew()
|
||||
.With(x => x.Name = "artist")
|
||||
.Build();
|
||||
}
|
||||
|
||||
private List<Track> GivenTracks(int count)
|
||||
{
|
||||
return Builder<Track>
|
||||
.CreateListOfSize(count)
|
||||
.All()
|
||||
.With(x => x.ArtistMetadata = artist)
|
||||
.Build()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private ParsedTrackInfo GivenParsedTrackInfo(Track track, AlbumRelease release)
|
||||
{
|
||||
return Builder<ParsedTrackInfo>
|
||||
.CreateNew()
|
||||
.With(x => x.Title = track.Title)
|
||||
.With(x => x.CleanTitle = track.Title.CleanTrackTitle())
|
||||
.With(x => x.AlbumTitle = release.Title)
|
||||
.With(x => x.Disambiguation = release.Disambiguation)
|
||||
.With(x => x.ReleaseMBId = release.ForeignReleaseId)
|
||||
.With(x => x.ArtistTitle = track.ArtistMetadata.Value.Name)
|
||||
.With(x => x.TrackNumbers = new[] { track.AbsoluteTrackNumber })
|
||||
.With(x => x.RecordingMBId = track.ForeignRecordingId)
|
||||
.With(x => x.Country = IsoCountries.Find("US"))
|
||||
.With(x => x.Label = release.Label.First())
|
||||
.With(x => x.Year = (uint)release.Album.Value.ReleaseDate.Value.Year)
|
||||
.Build();
|
||||
}
|
||||
|
||||
private List<LocalTrack> GivenLocalTracks(List<Track> tracks, AlbumRelease release)
|
||||
{
|
||||
var output = Builder<LocalTrack>
|
||||
.CreateListOfSize(tracks.Count)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
for (int i = 0; i < tracks.Count; i++)
|
||||
{
|
||||
output[i].FileTrackInfo = GivenParsedTrackInfo(tracks[i], release);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private AlbumRelease GivenAlbumRelease(string title, List<Track> tracks)
|
||||
{
|
||||
var album = Builder<Album>
|
||||
.CreateNew()
|
||||
.With(x => x.Title = title)
|
||||
.With(x => x.ArtistMetadata = artist)
|
||||
.Build();
|
||||
|
||||
var media = Builder<Medium>
|
||||
.CreateListOfSize(1)
|
||||
.Build()
|
||||
.ToList();
|
||||
|
||||
return Builder<AlbumRelease>
|
||||
.CreateNew()
|
||||
.With(x => x.Tracks = tracks)
|
||||
.With(x => x.Title = title)
|
||||
.With(x => x.Album = album)
|
||||
.With(x => x.Media = media)
|
||||
.With(x => x.Country = new List<string>())
|
||||
.With(x => x.Label = new List<string> { "label" })
|
||||
.Build();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_reorder_when_track_numbers_incorrect()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
|
||||
localTracks[2].FileTrackInfo.TrackNumbers = new [] { 2 };
|
||||
localTracks[1].FileTrackInfo.TrackNumbers = new [] { 3 };
|
||||
localTracks = new [] {0, 2, 1}.Select(x => localTracks[x]).ToList();
|
||||
|
||||
var result = Subject.MapReleaseTracks(localTracks, tracks);
|
||||
|
||||
result.Mapping
|
||||
.ToDictionary(x => x.Key, y => y.Value.Item1)
|
||||
.ShouldBeEquivalentTo(new Dictionary<LocalTrack, Track> {
|
||||
{localTracks[0], tracks[0]},
|
||||
{localTracks[1], tracks[2]},
|
||||
{localTracks[2], tracks[1]},
|
||||
});
|
||||
result.LocalExtra.Should().BeEmpty();
|
||||
result.MBExtra.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_order_works_with_invalid_track_numbers()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
|
||||
foreach (var track in localTracks)
|
||||
{
|
||||
track.FileTrackInfo.TrackNumbers = new[] { 1 };
|
||||
}
|
||||
|
||||
var result = Subject.MapReleaseTracks(localTracks, tracks);
|
||||
|
||||
result.Mapping
|
||||
.ToDictionary(x => x.Key, y => y.Value.Item1)
|
||||
.ShouldBeEquivalentTo(new Dictionary<LocalTrack, Track> {
|
||||
{localTracks[0], tracks[0]},
|
||||
{localTracks[1], tracks[1]},
|
||||
{localTracks[2], tracks[2]},
|
||||
});
|
||||
result.LocalExtra.Should().BeEmpty();
|
||||
result.MBExtra.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_order_works_with_missing_tracks()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
localTracks.RemoveAt(1);
|
||||
|
||||
var result = Subject.MapReleaseTracks(localTracks, tracks);
|
||||
|
||||
result.Mapping
|
||||
.ToDictionary(x => x.Key, y => y.Value.Item1)
|
||||
.ShouldBeEquivalentTo(new Dictionary<LocalTrack, Track> {
|
||||
{localTracks[0], tracks[0]},
|
||||
{localTracks[1], tracks[2]}
|
||||
});
|
||||
result.LocalExtra.Should().BeEmpty();
|
||||
result.MBExtra.ShouldBeEquivalentTo(new List<Track> { tracks[1] });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_order_works_with_extra_tracks()
|
||||
{
|
||||
var tracks = GivenTracks(3);
|
||||
var release = GivenAlbumRelease("album", tracks);
|
||||
var localTracks = GivenLocalTracks(tracks, release);
|
||||
tracks.RemoveAt(1);
|
||||
|
||||
var result = Subject.MapReleaseTracks(localTracks, tracks);
|
||||
|
||||
result.Mapping
|
||||
.ToDictionary(x => x.Key, y => y.Value.Item1)
|
||||
.ShouldBeEquivalentTo(new Dictionary<LocalTrack, Track> {
|
||||
{localTracks[0], tracks[0]},
|
||||
{localTracks[2], tracks[1]}
|
||||
});
|
||||
result.LocalExtra.ShouldBeEquivalentTo(new List<LocalTrack> { localTracks[1] });
|
||||
result.MBExtra.Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,178 +0,0 @@
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using System.Collections.Generic;
|
||||
using Moq;
|
||||
|
||||
namespace NzbDrone.Core.Test.MusicTests.TitleMatchingTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class TitleMatchingFixture : CoreTest<TrackService>
|
||||
{
|
||||
private List<Track> _tracks;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
var trackNames = new List<string> {
|
||||
"Courage",
|
||||
"Movies",
|
||||
"Flesh and Bone",
|
||||
"Whisper",
|
||||
"Summer",
|
||||
"Sticks and Stones",
|
||||
"Attitude",
|
||||
"Stranded",
|
||||
"Wish",
|
||||
"Calico",
|
||||
"(Happy) Death Day",
|
||||
"Smooth Criminal",
|
||||
"Universe / Orange Appeal",
|
||||
"Christian's Inferno"
|
||||
};
|
||||
|
||||
_tracks = new List<Track>();
|
||||
for (int i = 0; i < trackNames.Count; i++) {
|
||||
_tracks.Add(new Track
|
||||
{
|
||||
Title = trackNames[i],
|
||||
ForeignTrackId = (i+1).ToString(),
|
||||
AbsoluteTrackNumber = i+1,
|
||||
MediumNumber = 1
|
||||
});
|
||||
}
|
||||
|
||||
Mocker.GetMock<ITrackRepository>()
|
||||
.Setup(s => s.GetTracksByMedium(It.IsAny<int>(), It.IsAny<int>()))
|
||||
.Returns(_tracks);
|
||||
|
||||
Mocker.GetMock<ITrackRepository>()
|
||||
.Setup(s => s.Find(1234, 4321, It.IsAny<int>(), It.IsAny<int>()))
|
||||
.Returns((int artistid, int albumid, int medium, int track) => _tracks.Where(t => t.AbsoluteTrackNumber == track && t.MediumNumber == medium).Single());
|
||||
}
|
||||
|
||||
private void GivenSecondDisc()
|
||||
{
|
||||
var trackNames = new List<string> {
|
||||
"Courage",
|
||||
"another entry",
|
||||
"random name"
|
||||
};
|
||||
|
||||
for (int i = 0; i < trackNames.Count; i++) {
|
||||
_tracks.Add(new Track
|
||||
{
|
||||
Title = trackNames[i],
|
||||
ForeignTrackId = (100+i+1).ToString(),
|
||||
AbsoluteTrackNumber = i+1,
|
||||
MediumNumber = 2
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_find_track_in_db_by_tracktitle_longer_then_releasetitle()
|
||||
{
|
||||
var track = Subject.FindTrackByTitle(1234, 4321, 1, 1, "Courage with some bla");
|
||||
|
||||
track.Should().NotBeNull();
|
||||
track.Title.Should().Be(Subject.FindTrack(1234, 4321, 1, 1).Title);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_find_track_in_db_by_tracktitle_shorter_then_releasetitle()
|
||||
{
|
||||
var track = Subject.FindTrackByTitle(1234, 4321, 1, 3, "and Bone");
|
||||
|
||||
track.Should().NotBeNull();
|
||||
track.Title.Should().Be(Subject.FindTrack(1234, 4321, 1, 3).Title);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_find_track_in_db_by_wrong_title()
|
||||
{
|
||||
var track = Subject.FindTrackByTitle(1234, 4321, 1, 1, "Not a track");
|
||||
|
||||
track.Should().BeNull();
|
||||
}
|
||||
|
||||
[TestCase("another entry", 2, 2)]
|
||||
[TestCase("random name", 2, 3)]
|
||||
public void should_find_track_on_second_disc_when_disc_tag_missing(string title, int discNumber, int trackNumber)
|
||||
{
|
||||
GivenSecondDisc();
|
||||
var track = Subject.FindTrackByTitle(1234, 4321, 0, trackNumber, title);
|
||||
var expected = Subject.FindTrack(1234, 4321, discNumber, trackNumber);
|
||||
|
||||
track.Should().NotBeNull();
|
||||
expected.Should().NotBeNull();
|
||||
|
||||
track.Title.Should().Be(expected.Title);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_return_null_if_tracks_with_same_name_and_number_on_different_discs()
|
||||
{
|
||||
GivenSecondDisc();
|
||||
var track = Subject.FindTrackByTitle(1234, 4321, 0, 1, "Courage");
|
||||
track.Should().BeNull();
|
||||
}
|
||||
|
||||
[TestCase("Fesh and Bone", 3)]
|
||||
[TestCase("Atitude", 7)]
|
||||
[TestCase("Smoth cRimnal", 12)]
|
||||
[TestCase("Sticks and Stones (live)", 6)]
|
||||
[TestCase("Sticks and Stones (live) - there's a lot of rubbish here", 6)]
|
||||
[TestCase("Smoth cRimnal feat. someone I don't care about", 12)]
|
||||
[TestCase("Christians Inferno", 14)]
|
||||
[TestCase("xxxyyy some random prefix Christians Infurno", 14)]
|
||||
public void should_find_track_in_db_by_inexact_title(string title, int trackNumber)
|
||||
{
|
||||
var track = Subject.FindTrackByTitleInexact(1234, 4321, 1, trackNumber, title);
|
||||
var expected = Subject.FindTrack(1234, 4321, 1, trackNumber);
|
||||
|
||||
track.Should().NotBeNull();
|
||||
expected.Should().NotBeNull();
|
||||
|
||||
track.Title.Should().Be(expected.Title);
|
||||
}
|
||||
|
||||
[TestCase("Fesh and Bone", 1)]
|
||||
[TestCase("Atitude", 1)]
|
||||
[TestCase("Smoth cRimnal", 1)]
|
||||
[TestCase("Sticks and Stones (live)", 1)]
|
||||
[TestCase("Christians Inferno", 1)]
|
||||
public void should_not_find_track_in_db_by_inexact_title_with_wrong_tracknumber(string title, int trackNumber)
|
||||
{
|
||||
var track = Subject.FindTrackByTitleInexact(1234, 4321, 1, trackNumber, title);
|
||||
|
||||
track.Should().BeNull();
|
||||
}
|
||||
|
||||
[TestCase("Movis", 1, 2)]
|
||||
[TestCase("anoth entry", 2, 2)]
|
||||
[TestCase("random.name", 2, 3)]
|
||||
public void should_find_track_in_db_by_inexact_title_when_disc_tag_missing(string title, int discNumber, int trackNumber)
|
||||
{
|
||||
GivenSecondDisc();
|
||||
var track = Subject.FindTrackByTitleInexact(1234, 4321, 0, trackNumber, title);
|
||||
var expected = Subject.FindTrack(1234, 4321, discNumber, trackNumber);
|
||||
|
||||
track.Should().NotBeNull();
|
||||
expected.Should().NotBeNull();
|
||||
|
||||
track.Title.Should().Be(expected.Title);
|
||||
}
|
||||
|
||||
[TestCase("A random title", 1)]
|
||||
[TestCase("Stones and Sticks", 6)]
|
||||
public void should_not_find_track_in_db_by_different_inexact_title(string title, int trackId)
|
||||
{
|
||||
var track = Subject.FindTrackByTitleInexact(1234, 4321, 1, trackId, title);
|
||||
|
||||
track.Should().BeNull();
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -1,127 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class GetLocalTrackFixture : CoreTest<ParsingService>
|
||||
{
|
||||
private Artist _fakeArtist;
|
||||
private Album _fakeAlbum;
|
||||
private Track _fakeTrack;
|
||||
private ParsedTrackInfo _parsedTrackInfo;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_fakeArtist = Builder<Artist>
|
||||
.CreateNew()
|
||||
.Build();
|
||||
|
||||
_fakeAlbum = Builder<Album>
|
||||
.CreateNew()
|
||||
.With(e => e.ArtistId = _fakeArtist.Id)
|
||||
.With(e => e.AlbumReleases = new List<AlbumRelease>
|
||||
{
|
||||
new AlbumRelease
|
||||
{
|
||||
ForeignReleaseId = "5ecd552b-e54b-4c37-b62c-9d6234834bad",
|
||||
Monitored = true
|
||||
}
|
||||
})
|
||||
.Build();
|
||||
|
||||
_fakeTrack = Builder<Track>
|
||||
.CreateNew()
|
||||
.With(e => e.Artist = _fakeArtist)
|
||||
.With(e => e.AlbumId = _fakeAlbum.Id)
|
||||
.With(e => e.Album = null)
|
||||
.Build();
|
||||
|
||||
_parsedTrackInfo = Builder<ParsedTrackInfo>
|
||||
.CreateNew()
|
||||
.With(e => e.AlbumTitle = _fakeAlbum.Title)
|
||||
.With(e => e.Title = _fakeTrack.Title)
|
||||
.With(e => e.ArtistTitle = _fakeArtist.Name)
|
||||
.Build();
|
||||
|
||||
Mocker.GetMock<IAlbumService>()
|
||||
.Setup(s => s.FindByTitle(_fakeArtist.Id,_fakeAlbum.Title))
|
||||
.Returns(_fakeAlbum);
|
||||
|
||||
Mocker.GetMock<IAlbumService>()
|
||||
.Setup(s => s.FindAlbumByRelease(_fakeAlbum.AlbumReleases.Value.First().ForeignReleaseId))
|
||||
.Returns(_fakeAlbum);
|
||||
|
||||
Mocker.GetMock<ITrackService>()
|
||||
.Setup(s => s.FindTrackByTitle(_fakeArtist.Id, _fakeAlbum.Id, It.IsAny<int>(), It.IsAny<int>(), _fakeTrack.Title))
|
||||
.Returns(_fakeTrack);
|
||||
}
|
||||
|
||||
private void HasAlbumTitleNoReleaseId()
|
||||
{
|
||||
_parsedTrackInfo.AlbumTitle = _fakeAlbum.Title;
|
||||
_parsedTrackInfo.ReleaseMBId = "";
|
||||
}
|
||||
|
||||
private void HasReleaseMbIdNoTitle()
|
||||
{
|
||||
_parsedTrackInfo.AlbumTitle = "";
|
||||
_parsedTrackInfo.ReleaseMBId = _fakeAlbum.AlbumReleases.Value.First().ForeignReleaseId;
|
||||
}
|
||||
|
||||
private void HasNoReleaseIdOrTitle()
|
||||
{
|
||||
_parsedTrackInfo.AlbumTitle = "";
|
||||
_parsedTrackInfo.ReleaseMBId = "";
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_find_album_with_title_no_MBID()
|
||||
{
|
||||
HasAlbumTitleNoReleaseId();
|
||||
|
||||
var localTrack = Subject.GetLocalTrack("somfile.mp3", _fakeArtist, _parsedTrackInfo);
|
||||
|
||||
localTrack.Artist.Id.Should().Be(_fakeArtist.Id);
|
||||
localTrack.Album.Id.Should().Be(_fakeAlbum.Id);
|
||||
localTrack.Tracks.First().Id.Should().Be(_fakeTrack.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_find_album_with_release_MBID_no_title()
|
||||
{
|
||||
HasReleaseMbIdNoTitle();
|
||||
|
||||
var localTrack = Subject.GetLocalTrack("somfile.mp3", _fakeArtist, _parsedTrackInfo);
|
||||
|
||||
localTrack.Artist.Id.Should().Be(_fakeArtist.Id);
|
||||
localTrack.Album.Id.Should().Be(_fakeAlbum.Id);
|
||||
localTrack.Tracks.First().Id.Should().Be(_fakeTrack.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_find_album_with_no_release_MBID_no_title()
|
||||
{
|
||||
HasNoReleaseIdOrTitle();
|
||||
|
||||
var localTrack = Subject.GetLocalTrack("somfile.mp3", _fakeArtist, _parsedTrackInfo);
|
||||
ExceptionVerification.ExpectedWarns(1);
|
||||
|
||||
localTrack.Should().BeNull();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
namespace NzbDrone.Core.Configuration
|
||||
{
|
||||
public enum AllowFingerprinting
|
||||
{
|
||||
Never,
|
||||
NewFiles,
|
||||
AllFiles
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using FluentMigrator;
|
||||
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Datastore.Migration
|
||||
{
|
||||
[Migration(024)]
|
||||
public class NewMediaInfoFormat : NzbDroneMigrationBase
|
||||
{
|
||||
protected override void MainDbUpgrade()
|
||||
{
|
||||
Update.Table("TrackFiles").Set(new { MediaInfo = "" }).AllRows();
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue