mirror of
https://github.com/Radarr/Radarr.git
synced 2026-03-16 16:04:52 -04:00
Compare commits
38 Commits
v4.3.0.667
...
sonarr-pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
676dcbae0e | ||
|
|
5a7a9db7ed | ||
|
|
182cda47b0 | ||
|
|
294d95fae4 | ||
|
|
0e3f871e0e | ||
|
|
b0f5f02edc | ||
|
|
2afe6af5a6 | ||
|
|
e2eaf91aa7 | ||
|
|
0e1c2c3c50 | ||
|
|
69cf2e89a6 | ||
|
|
9830230589 | ||
|
|
b1f0b2c216 | ||
|
|
7c6858ecfb | ||
|
|
ee32d42c94 | ||
|
|
3390df4085 | ||
|
|
01bc5f6fc8 | ||
|
|
2d867a6cb6 | ||
|
|
411f8866ec | ||
|
|
5316382113 | ||
|
|
8fe81b428a | ||
|
|
43a2a2d335 | ||
|
|
5c8b58c30d | ||
|
|
131a223bb9 | ||
|
|
dfaab639bf | ||
|
|
c7be63d48f | ||
|
|
2958faf4a8 | ||
|
|
4280df8b61 | ||
|
|
1f91be6407 | ||
|
|
eb43a3c2d0 | ||
|
|
20c7e84676 | ||
|
|
691a8955fe | ||
|
|
53a9c849cb | ||
|
|
856a55a9c9 | ||
|
|
43cd536746 | ||
|
|
4a205d8041 | ||
|
|
2525ac2d1a | ||
|
|
e5ceb20a83 | ||
|
|
0f6b11f55d |
132
CODE_OF_CONDUCT.md
Normal file
132
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
<development@radarr.video>.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
@@ -9,7 +9,7 @@ variables:
|
||||
testsFolder: './_tests'
|
||||
yarnCacheFolder: $(Pipeline.Workspace)/.yarn
|
||||
nugetCacheFolder: $(Pipeline.Workspace)/.nuget/packages
|
||||
majorVersion: '4.3.0'
|
||||
majorVersion: '4.3.1'
|
||||
minorVersion: $[counter('minorVersion', 2000)]
|
||||
radarrVersion: '$(majorVersion).$(minorVersion)'
|
||||
buildName: '$(Build.SourceBranchName).$(radarrVersion)'
|
||||
@@ -761,7 +761,7 @@ stages:
|
||||
inputs:
|
||||
buildType: 'current'
|
||||
artifactName: Packages
|
||||
itemPattern: '/$(pattern)'
|
||||
itemPattern: '**/$(pattern)'
|
||||
targetPath: $(Build.ArtifactStagingDirectory)
|
||||
- bash: |
|
||||
mkdir -p ${BUILD_ARTIFACTSTAGINGDIRECTORY}/bin
|
||||
@@ -1143,4 +1143,5 @@ stages:
|
||||
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
||||
DISCORDCHANNELID: $(discordChannelId)
|
||||
DISCORDWEBHOOKKEY: $(discordWebhookKey)
|
||||
DISCORDTHREADID: $(discordThreadId)
|
||||
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.customFormatScore {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 55px;
|
||||
}
|
||||
|
||||
.releaseGroup {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import MovieFormats from 'Movie/MovieFormats';
|
||||
import MovieLanguage from 'Movie/MovieLanguage';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||
import HistoryDetailsModal from './Details/HistoryDetailsModal';
|
||||
import HistoryEventTypeCell from './HistoryEventTypeCell';
|
||||
import styles from './HistoryRow.css';
|
||||
@@ -168,6 +169,17 @@ class HistoryRow extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'customFormatScore') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.customFormatScore}
|
||||
>
|
||||
{formatCustomFormatScore(data.customFormatScore)}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'releaseGroup') {
|
||||
return (
|
||||
<TableRowCell
|
||||
|
||||
@@ -192,7 +192,7 @@ class TableOptionsModal extends Component {
|
||||
<TableOptionsColumnDragSource
|
||||
key={name}
|
||||
name={name}
|
||||
label={label || columnLabel}
|
||||
label={columnLabel || label}
|
||||
isVisible={isVisible}
|
||||
isModifiable={true}
|
||||
index={index}
|
||||
@@ -210,7 +210,7 @@ class TableOptionsModal extends Component {
|
||||
<TableOptionsColumn
|
||||
key={name}
|
||||
name={name}
|
||||
label={label || columnLabel}
|
||||
label={columnLabel || label}
|
||||
isVisible={isVisible}
|
||||
index={index}
|
||||
isModifiable={false}
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
import {
|
||||
faArrowCircleLeft as fasArrowCircleLeft,
|
||||
faArrowCircleRight as fasArrowCircleRight,
|
||||
faAsterisk as fasAsterisk,
|
||||
faBackward as fasBackward,
|
||||
faBan as fasBan,
|
||||
faBars as fasBars,
|
||||
@@ -154,6 +155,7 @@ export const FILE = farFile;
|
||||
export const FILM = fasFilm;
|
||||
export const FILTER = fasFilter;
|
||||
export const FLAG = fasFlag;
|
||||
export const FOOTNOTE = fasAsterisk;
|
||||
export const FOLDER = farFolder;
|
||||
export const FOLDER_OPEN = fasFolderOpen;
|
||||
export const GENRE = fasTheaterMasks;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
.modalBody {
|
||||
composes: modalBody from '~Components/Modal/ModalBody.css';
|
||||
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -9,8 +9,9 @@ import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import { inputTypes, kinds, scrollDirections } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './SelectReleaseGroupModalContent.css';
|
||||
|
||||
class SelectReleaseGroupModalContent extends Component {
|
||||
|
||||
@@ -58,7 +59,10 @@ class SelectReleaseGroupModalContent extends Component {
|
||||
{translate('ManualImportSetReleaseGroup')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<ModalBody
|
||||
className={styles.modalBody}
|
||||
scrollDirection={scrollDirections.NONE}
|
||||
>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ReleaseGroup')}</FormLabel>
|
||||
@@ -67,6 +71,7 @@ class SelectReleaseGroupModalContent extends Component {
|
||||
type={inputTypes.TEXT}
|
||||
name="releaseGroup"
|
||||
value={releaseGroup}
|
||||
autoFocus={true}
|
||||
onChange={this.onReleaseGroupChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { icons, kinds } from 'Helpers/Props';
|
||||
import MovieFormats from 'Movie/MovieFormats';
|
||||
import MovieLanguage from 'Movie/MovieLanguage';
|
||||
import MovieQuality from 'Movie/MovieQuality';
|
||||
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './MovieHistoryRow.css';
|
||||
|
||||
@@ -104,6 +105,10 @@ class MovieHistoryRow extends Component {
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell key={name}>
|
||||
{formatCustomFormatScore(data.customFormatScore)}
|
||||
</TableRowCell>
|
||||
|
||||
<RelativeDateCellConnector
|
||||
date={date}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import Table from 'Components/Table/Table';
|
||||
@@ -35,6 +36,15 @@ const columns = [
|
||||
isSortable: false,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'customFormatScore',
|
||||
label: React.createElement(Icon, {
|
||||
name: icons.SCORE,
|
||||
title: 'Custom format score'
|
||||
}),
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'date',
|
||||
label: translate('Date'),
|
||||
|
||||
@@ -36,3 +36,9 @@
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
composes: label from '~Components/Label.css';
|
||||
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import MiddleTruncate from 'react-middle-truncate';
|
||||
import Card from 'Components/Card';
|
||||
import Label from 'Components/Label';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
@@ -124,10 +125,15 @@ class CustomFormat extends Component {
|
||||
|
||||
return (
|
||||
<Label
|
||||
className={styles.label}
|
||||
key={index}
|
||||
kind={kind}
|
||||
>
|
||||
{item.name}
|
||||
<MiddleTruncate
|
||||
text={item.name}
|
||||
start={10}
|
||||
end={14}
|
||||
/>
|
||||
</Label>
|
||||
);
|
||||
})
|
||||
|
||||
@@ -16,3 +16,20 @@
|
||||
margin-left: 10px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.footNote {
|
||||
display: flex;
|
||||
color: $helpTextColor;
|
||||
|
||||
.icon {
|
||||
margin-top: 3px;
|
||||
margin-right: 5px;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 0 1px;
|
||||
border: 1px solid $borderColor;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,17 +3,94 @@ import React, { Component } from 'react';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import SelectInput from 'Components/Form/SelectInput';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import Icon from 'Components/Icon';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import { icons, sizes } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import NamingOption from './NamingOption';
|
||||
import styles from './NamingModal.css';
|
||||
|
||||
const separatorOptions = [
|
||||
{ key: ' ', value: 'Space ( )' },
|
||||
{ key: '.', value: 'Period (.)' },
|
||||
{ key: '_', value: 'Underscore (_)' },
|
||||
{ key: '-', value: 'Dash (-)' }
|
||||
];
|
||||
|
||||
const caseOptions = [
|
||||
{ key: 'title', value: translate('DefaultCase') },
|
||||
{ key: 'lower', value: translate('LowerCase') },
|
||||
{ key: 'upper', value: translate('UpperCase') }
|
||||
];
|
||||
|
||||
const fileNameTokens = [
|
||||
{
|
||||
token: '{Movie Title} - {Quality Full}',
|
||||
example: 'Movie Title (2010) - HDTV-720p Proper'
|
||||
}
|
||||
];
|
||||
|
||||
const movieTokens = [
|
||||
{ token: '{Movie Title}', example: 'Movie\'s Title' },
|
||||
{ token: '{Movie Title:DE}', example: 'Titel des Films' },
|
||||
{ token: '{Movie CleanTitle}', example: 'Movies Title' },
|
||||
{ token: '{Movie TitleThe}', example: 'Movie\'s Title, The' },
|
||||
{ token: '{Movie OriginalTitle}', example: 'Τίτλος ταινίας' },
|
||||
{ token: '{Movie CleanOriginalTitle}', example: 'Τίτλος ταινίας' },
|
||||
{ token: '{Movie TitleFirstCharacter}', example: 'M' },
|
||||
{ token: '{Movie Collection}', example: 'The Movie Collection' },
|
||||
{ token: '{Movie Certification}', example: 'R' },
|
||||
{ token: '{Release Year}', example: '2009' }
|
||||
];
|
||||
|
||||
const movieIdTokens = [
|
||||
{ token: '{ImdbId}', example: 'tt12345' },
|
||||
{ token: '{TmdbId}', example: '123456' }
|
||||
];
|
||||
|
||||
const qualityTokens = [
|
||||
{ token: '{Quality Full}', example: 'HDTV-720p Proper' },
|
||||
{ token: '{Quality Title}', example: 'HDTV-720p' }
|
||||
];
|
||||
|
||||
const mediaInfoTokens = [
|
||||
{ token: '{MediaInfo Simple}', example: 'x264 DTS' },
|
||||
{ token: '{MediaInfo Full}', example: 'x264 DTS [EN+DE]', footNote: 1 },
|
||||
|
||||
{ token: '{MediaInfo AudioCodec}', example: 'DTS' },
|
||||
{ token: '{MediaInfo AudioChannels}', example: '5.1' },
|
||||
{ token: '{MediaInfo AudioLanguages}', example: '[EN+DE]', footNote: 1 },
|
||||
{ token: '{MediaInfo SubtitleLanguages}', example: '[DE]', footNote: 1 },
|
||||
|
||||
{ token: '{MediaInfo VideoCodec}', example: 'x264' },
|
||||
{ token: '{MediaInfo VideoBitDepth}', example: '10' },
|
||||
{ token: '{MediaInfo VideoDynamicRange}', example: 'HDR' },
|
||||
{ token: '{MediaInfo VideoDynamicRangeType}', example: 'DV HDR10' },
|
||||
{ token: '{MediaInfo 3D}', example: '3D' }
|
||||
];
|
||||
|
||||
const releaseGroupTokens = [
|
||||
{ token: '{Release Group}', example: 'Rls Grp' }
|
||||
];
|
||||
|
||||
const editionTokens = [
|
||||
{ token: '{Edition Tags}', example: 'IMAX' }
|
||||
];
|
||||
|
||||
const customFormatTokens = [
|
||||
{ token: '{Custom Formats}', example: 'Surround Sound x264' }
|
||||
];
|
||||
|
||||
const originalTokens = [
|
||||
{ token: '{Original Title}', example: 'Movie.Title.HDTV.x264-EVOLVE' },
|
||||
{ token: '{Original Filename}', example: 'movie title hdtv.x264-Evolve' }
|
||||
];
|
||||
|
||||
class NamingModal extends Component {
|
||||
|
||||
//
|
||||
@@ -94,81 +171,6 @@ class NamingModal extends Component {
|
||||
case: tokenCase
|
||||
} = this.state;
|
||||
|
||||
const separatorOptions = [
|
||||
{ key: ' ', value: 'Space ( )' },
|
||||
{ key: '.', value: 'Period (.)' },
|
||||
{ key: '_', value: 'Underscore (_)' },
|
||||
{ key: '-', value: 'Dash (-)' }
|
||||
];
|
||||
|
||||
const caseOptions = [
|
||||
{ key: 'title', value: translate('DefaultCase') },
|
||||
{ key: 'lower', value: translate('LowerCase') },
|
||||
{ key: 'upper', value: translate('UpperCase') }
|
||||
];
|
||||
|
||||
const fileNameTokens = [
|
||||
{
|
||||
token: '{Movie Title} - {Quality Full}',
|
||||
example: 'Movie Title (2010) - HDTV-720p Proper'
|
||||
}
|
||||
];
|
||||
|
||||
const movieTokens = [
|
||||
{ token: '{Movie Title}', example: 'Movie\'s Title' },
|
||||
{ token: '{Movie Title:DE}', example: 'Titel des Films' },
|
||||
{ token: '{Movie CleanTitle}', example: 'Movies Title' },
|
||||
{ token: '{Movie TitleThe}', example: 'Movie\'s Title, The' },
|
||||
{ token: '{Movie OriginalTitle}', example: 'Τίτλος ταινίας' },
|
||||
{ token: '{Movie CleanOriginalTitle}', example: 'Τίτλος ταινίας' },
|
||||
{ token: '{Movie TitleFirstCharacter}', example: 'M' },
|
||||
{ token: '{Movie Collection}', example: 'The Movie Collection' },
|
||||
{ token: '{Movie Certification}', example: 'R' },
|
||||
{ token: '{Release Year}', example: '2009' }
|
||||
];
|
||||
|
||||
const movieIdTokens = [
|
||||
{ token: '{ImdbId}', example: 'tt12345' },
|
||||
{ token: '{TmdbId}', example: '123456' }
|
||||
];
|
||||
|
||||
const qualityTokens = [
|
||||
{ token: '{Quality Full}', example: 'HDTV-720p Proper' },
|
||||
{ token: '{Quality Title}', example: 'HDTV-720p' }
|
||||
];
|
||||
|
||||
const mediaInfoTokens = [
|
||||
{ token: '{MediaInfo Simple}', example: 'x264 DTS' },
|
||||
{ token: '{MediaInfo Full}', example: 'x264 DTS [EN+DE]' },
|
||||
|
||||
{ token: '{MediaInfo AudioCodec}', example: 'DTS' },
|
||||
{ token: '{MediaInfo AudioChannels}', example: '5.1' },
|
||||
{ token: '{MediaInfo AudioLanguages}', example: '[EN+DE]' },
|
||||
{ token: '{MediaInfo SubtitleLanguages}', example: '[DE]' },
|
||||
|
||||
{ token: '{MediaInfo VideoCodec}', example: 'x264' },
|
||||
{ token: '{MediaInfo VideoBitDepth}', example: '10' },
|
||||
{ token: '{MediaInfo VideoDynamicRange}', example: 'HDR' },
|
||||
{ token: '{MediaInfo VideoDynamicRangeType}', example: 'DV HDR10' }
|
||||
];
|
||||
|
||||
const releaseGroupTokens = [
|
||||
{ token: '{Release Group}', example: 'Rls Grp' }
|
||||
];
|
||||
|
||||
const editionTokens = [
|
||||
{ token: '{Edition Tags}', example: 'IMAX' }
|
||||
];
|
||||
|
||||
const customFormatTokens = [
|
||||
{ token: '{Custom Formats}', example: 'Surround Sound x264' }
|
||||
];
|
||||
|
||||
const originalTokens = [
|
||||
{ token: '{Original Title}', example: 'Movie.Title.HDTV.x264-EVOLVE' },
|
||||
{ token: '{Original Filename}', example: 'movie title hdtv.x264-Evolve' }
|
||||
];
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
@@ -297,7 +299,7 @@ class NamingModal extends Component {
|
||||
<FieldSet legend={translate('MediaInfo')}>
|
||||
<div className={styles.groups}>
|
||||
{
|
||||
mediaInfoTokens.map(({ token, example }) => {
|
||||
mediaInfoTokens.map(({ token, example, footNote }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
@@ -305,6 +307,7 @@ class NamingModal extends Component {
|
||||
value={value}
|
||||
token={token}
|
||||
example={example}
|
||||
footNote={footNote}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={this.onOptionPress}
|
||||
@@ -314,6 +317,14 @@ class NamingModal extends Component {
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className={styles.footNote}>
|
||||
<Icon className={styles.icon} name={icons.FOOTNOTE} />
|
||||
<div>
|
||||
MediaInfo Full/AudioLanguages/SubtitleLanguages support a <code>:EN+DE</code> suffix allowing you to filter the languages included in the filename. Use <code>-DE</code> to exclude specific languages.
|
||||
Appending <code>+</code> (eg <code>:EN+</code>) will output <code>[EN]</code>/<code>[EN+--]</code>/<code>[--]</code> depending on excluded languages. For example <code>{'{'}MediaInfo Full:EN+DE{'}'}</code>.
|
||||
</div>
|
||||
</div>
|
||||
</FieldSet>
|
||||
|
||||
<FieldSet legend={translate('ReleaseGroup')}>
|
||||
|
||||
@@ -35,9 +35,15 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
justify-content: space-between;
|
||||
flex: 0 0 50%;
|
||||
padding: 6px 16px;
|
||||
background-color: #ddd;
|
||||
|
||||
.footNote {
|
||||
padding: 2px;
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
|
||||
.lower {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import Link from 'Components/Link/Link';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import { icons, sizes } from 'Helpers/Props';
|
||||
import styles from './NamingOption.css';
|
||||
|
||||
class NamingOption extends Component {
|
||||
@@ -39,6 +40,7 @@ class NamingOption extends Component {
|
||||
token,
|
||||
tokenSeparator,
|
||||
example,
|
||||
footNote,
|
||||
tokenCase,
|
||||
isFullFilename,
|
||||
size
|
||||
@@ -60,6 +62,11 @@ class NamingOption extends Component {
|
||||
|
||||
<div className={styles.example}>
|
||||
{example.replace(/ /g, tokenSeparator)}
|
||||
|
||||
{
|
||||
footNote !== 0 &&
|
||||
<Icon className={styles.footNote} name={icons.FOOTNOTE} />
|
||||
}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
@@ -69,6 +76,7 @@ class NamingOption extends Component {
|
||||
NamingOption.propTypes = {
|
||||
token: PropTypes.string.isRequired,
|
||||
example: PropTypes.string.isRequired,
|
||||
footNote: PropTypes.number.isRequired,
|
||||
tokenSeparator: PropTypes.string.isRequired,
|
||||
tokenCase: PropTypes.string.isRequired,
|
||||
isFullFilename: PropTypes.bool.isRequired,
|
||||
@@ -77,6 +85,7 @@ NamingOption.propTypes = {
|
||||
};
|
||||
|
||||
NamingOption.defaultProps = {
|
||||
footNote: 0,
|
||||
size: sizes.SMALL,
|
||||
isFullFilename: false
|
||||
};
|
||||
|
||||
@@ -85,7 +85,7 @@ class DelayProfile extends Component {
|
||||
connectDragSource
|
||||
} = this.props;
|
||||
|
||||
let preferred = titleCase(preferredProtocol);
|
||||
let preferred = `Prefer ${titleCase(preferredProtocol)}`;
|
||||
|
||||
if (!enableUsenet) {
|
||||
preferred = translate('OnlyTorrent');
|
||||
|
||||
@@ -83,7 +83,7 @@ class DelayProfiles extends Component {
|
||||
<div>
|
||||
<div className={styles.delayProfilesHeader}>
|
||||
<div className={styles.column}>
|
||||
{translate('Protocol')}
|
||||
{translate('PreferredProtocol')}
|
||||
</div>
|
||||
<div className={styles.column}>
|
||||
{translate('UsenetDelay')}
|
||||
|
||||
@@ -17,6 +17,13 @@ import { boolSettingShape, numberSettingShape, tagSettingShape } from 'Helpers/P
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './EditDelayProfileModalContent.css';
|
||||
|
||||
const protocolOptions = [
|
||||
{ key: 'preferUsenet', value: translate('PreferUsenet') },
|
||||
{ key: 'preferTorrent', value: translate('PreferTorrent') },
|
||||
{ key: 'onlyUsenet', value: translate('OnlyUsenet') },
|
||||
{ key: 'onlyTorrent', value: translate('OnlyTorrent') }
|
||||
];
|
||||
|
||||
function EditDelayProfileModalContent(props) {
|
||||
const {
|
||||
id,
|
||||
@@ -26,7 +33,6 @@ function EditDelayProfileModalContent(props) {
|
||||
saveError,
|
||||
item,
|
||||
protocol,
|
||||
protocolOptions,
|
||||
onInputChange,
|
||||
onProtocolChange,
|
||||
onSavePress,
|
||||
@@ -52,22 +58,24 @@ function EditDelayProfileModalContent(props) {
|
||||
|
||||
<ModalBody>
|
||||
{
|
||||
isFetching &&
|
||||
<LoadingIndicator />
|
||||
isFetching ?
|
||||
<LoadingIndicator /> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
!isFetching && !!error ?
|
||||
<div>
|
||||
{translate('UnableToAddANewQualityProfilePleaseTryAgain')}
|
||||
</div>
|
||||
</div> :
|
||||
null
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !error &&
|
||||
!isFetching && !error ?
|
||||
<Form {...otherProps}>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Protocol')}</FormLabel>
|
||||
<FormLabel>{translate('PreferredProtocol')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
@@ -143,19 +151,21 @@ function EditDelayProfileModalContent(props) {
|
||||
/>
|
||||
</FormGroup>
|
||||
}
|
||||
</Form>
|
||||
</Form> :
|
||||
null
|
||||
}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
{
|
||||
id && id > 1 &&
|
||||
id && id > 1 ?
|
||||
<Button
|
||||
className={styles.deleteButton}
|
||||
kind={kinds.DANGER}
|
||||
onPress={onDeleteDelayProfilePress}
|
||||
>
|
||||
{translate('Delete')}
|
||||
</Button>
|
||||
</Button> :
|
||||
null
|
||||
}
|
||||
|
||||
<Button
|
||||
@@ -193,7 +203,6 @@ EditDelayProfileModalContent.propTypes = {
|
||||
saveError: PropTypes.object,
|
||||
item: PropTypes.shape(delayProfileShape).isRequired,
|
||||
protocol: PropTypes.string.isRequired,
|
||||
protocolOptions: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onInputChange: PropTypes.func.isRequired,
|
||||
onProtocolChange: PropTypes.func.isRequired,
|
||||
onSavePress: PropTypes.func.isRequired,
|
||||
|
||||
@@ -5,7 +5,6 @@ import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { saveDelayProfile, setDelayProfileValue } from 'Store/Actions/settingsActions';
|
||||
import selectSettings from 'Store/Selectors/selectSettings';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EditDelayProfileModalContent from './EditDelayProfileModalContent';
|
||||
|
||||
const newDelayProfile = {
|
||||
@@ -17,13 +16,6 @@ const newDelayProfile = {
|
||||
tags: []
|
||||
};
|
||||
|
||||
const protocolOptions = [
|
||||
{ key: 'preferUsenet', value: translate('PreferUsenet') },
|
||||
{ key: 'preferTorrent', value: translate('PreferTorrent') },
|
||||
{ key: 'onlyUsenet', value: translate('OnlyUsenet') },
|
||||
{ key: 'onlyTorrent', value: translate('OnlyTorrent') }
|
||||
];
|
||||
|
||||
function createDelayProfileSelector() {
|
||||
return createSelector(
|
||||
(state, { id }) => id,
|
||||
@@ -79,7 +71,6 @@ function createMapStateToProps() {
|
||||
|
||||
return {
|
||||
protocol,
|
||||
protocolOptions,
|
||||
...delayProfile
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
import { createAction } from 'redux-actions';
|
||||
import { filterTypes, sortDirections } from 'Helpers/Props';
|
||||
import Icon from 'Components/Icon';
|
||||
import { filterTypes, icons, sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import serverSideCollectionHandlers from 'Utilities/serverSideCollectionHandlers';
|
||||
@@ -84,6 +86,15 @@ export const defaultState = {
|
||||
label: translate('SourceTitle'),
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'customFormatScore',
|
||||
columnLabel: translate('CustomFormatScore'),
|
||||
label: React.createElement(Icon, {
|
||||
name: icons.SCORE,
|
||||
title: 'Custom format score'
|
||||
}),
|
||||
isVisible: false
|
||||
},
|
||||
{
|
||||
name: 'details',
|
||||
columnLabel: translate('Details'),
|
||||
|
||||
@@ -4,6 +4,7 @@ import { batchActions } from 'redux-batched-actions';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import naturalExpansion from 'Utilities/String/naturalExpansion';
|
||||
import { set, update, updateItem } from './baseActions';
|
||||
import createHandleActions from './Creators/createHandleActions';
|
||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
||||
@@ -35,7 +36,7 @@ export const defaultState = {
|
||||
relativePath: function(item, direction) {
|
||||
const relativePath = item.relativePath;
|
||||
|
||||
return relativePath.toLowerCase();
|
||||
return naturalExpansion(relativePath.toLowerCase());
|
||||
},
|
||||
|
||||
movie: function(item, direction) {
|
||||
|
||||
16
frontend/src/Utilities/Number/formatCustomFormatScore.js
Normal file
16
frontend/src/Utilities/Number/formatCustomFormatScore.js
Normal file
@@ -0,0 +1,16 @@
|
||||
|
||||
function formatCustomFormatScore(input) {
|
||||
const score = Number(input);
|
||||
|
||||
if (score > 0) {
|
||||
return `+${score}`;
|
||||
}
|
||||
|
||||
if (score < 0) {
|
||||
return score;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
export default formatCustomFormatScore;
|
||||
11
frontend/src/Utilities/String/naturalExpansion.js
Normal file
11
frontend/src/Utilities/String/naturalExpansion.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const regex = /\d+/g;
|
||||
|
||||
function naturalExpansion(input) {
|
||||
if (!input) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return input.replace(regex, (n) => n.padStart(8, '0'));
|
||||
}
|
||||
|
||||
export default naturalExpansion;
|
||||
@@ -1,9 +1,11 @@
|
||||
const regex = /\b\w+/g;
|
||||
|
||||
function titleCase(input) {
|
||||
if (!input) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return input.replace(/\b\w+/g, (match) => {
|
||||
return input.replace(regex, (match) => {
|
||||
return match.charAt(0).toUpperCase() + match.substr(1).toLowerCase();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"jquery": "3.6.0",
|
||||
"lodash": "4.17.21",
|
||||
"mobile-detect": "1.4.5",
|
||||
"moment": "2.29.2",
|
||||
"moment": "2.29.4",
|
||||
"mousetrap": "1.6.5",
|
||||
"normalize.css": "8.0.1",
|
||||
"prop-types": "15.7.2",
|
||||
|
||||
@@ -212,6 +212,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Platform(Exclude = "MacOsX", Reason = "Azure agent update prevents brotli on OSX")]
|
||||
public void should_execute_get_using_brotli()
|
||||
{
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/brotli");
|
||||
|
||||
@@ -80,6 +80,10 @@ namespace NzbDrone.Common.Test.InstrumentationTests
|
||||
// Notifiarr
|
||||
[TestCase(@"https://xxx.yyy/api/v1/notification/radarr/9pr04sg6-0123-3210-imav-eql2tyu8xyui")]
|
||||
|
||||
// Discord
|
||||
[TestCase(@"https://discord.com/api/webhooks/mySecret")]
|
||||
[TestCase(@"https://discord.com/api/webhooks/mySecret/01233210")]
|
||||
|
||||
public void should_clean_message(string message)
|
||||
{
|
||||
var cleansedMessage = CleanseLogMessage.Cleanse(message);
|
||||
|
||||
@@ -278,7 +278,7 @@ namespace NzbDrone.Common.Test
|
||||
[Test]
|
||||
public void GetUpdateClientExePath()
|
||||
{
|
||||
GetIAppDirectoryInfo().GetUpdateClientExePath(PlatformType.DotNet).Should().BeEquivalentTo(@"C:\Temp\radarr_update\Radarr.Update.exe".AsOsAgnostic());
|
||||
GetIAppDirectoryInfo().GetUpdateClientExePath().Should().BeEquivalentTo(@"C:\Temp\radarr_update\Radarr.Update".AsOsAgnostic().ProcessNameToExe());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -264,6 +264,11 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
protected virtual void MoveFileInternal(string source, string destination)
|
||||
{
|
||||
if (File.Exists(destination))
|
||||
{
|
||||
throw new FileAlreadyExistsException("File already exists", destination);
|
||||
}
|
||||
|
||||
File.Move(source, destination);
|
||||
}
|
||||
|
||||
|
||||
15
src/NzbDrone.Common/Disk/FileAlreadyExistsException.cs
Normal file
15
src/NzbDrone.Common/Disk/FileAlreadyExistsException.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Common.Disk
|
||||
{
|
||||
public class FileAlreadyExistsException : Exception
|
||||
{
|
||||
public string Filename { get; set; }
|
||||
|
||||
public FileAlreadyExistsException(string message, string filename)
|
||||
: base(message)
|
||||
{
|
||||
Filename = filename;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,6 @@ using System;
|
||||
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
{
|
||||
public enum PlatformType
|
||||
{
|
||||
DotNet = 0,
|
||||
Mono = 1,
|
||||
NetCore = 2
|
||||
}
|
||||
|
||||
public interface IPlatformInfo
|
||||
{
|
||||
Version Version { get; }
|
||||
@@ -16,31 +9,18 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
|
||||
public class PlatformInfo : IPlatformInfo
|
||||
{
|
||||
private static PlatformType _platform;
|
||||
private static Version _version;
|
||||
|
||||
static PlatformInfo()
|
||||
{
|
||||
_platform = PlatformType.NetCore;
|
||||
_version = Environment.Version;
|
||||
}
|
||||
|
||||
public static PlatformType Platform => _platform;
|
||||
public static bool IsDotNet => Platform == PlatformType.DotNet;
|
||||
public static bool IsNetCore => Platform == PlatformType.NetCore;
|
||||
|
||||
public static string PlatformName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsDotNet)
|
||||
{
|
||||
return ".NET";
|
||||
}
|
||||
else
|
||||
{
|
||||
return ".NET Core";
|
||||
}
|
||||
return ".NET";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -267,9 +267,9 @@ namespace NzbDrone.Common.Extensions
|
||||
return substring.Substring(0, lastSeparatorIndex);
|
||||
}
|
||||
|
||||
public static string ProcessNameToExe(this string processName, PlatformType runtime)
|
||||
public static string ProcessNameToExe(this string processName)
|
||||
{
|
||||
if (OsInfo.IsWindows || runtime != PlatformType.NetCore)
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
processName += ".exe";
|
||||
}
|
||||
@@ -277,11 +277,6 @@ namespace NzbDrone.Common.Extensions
|
||||
return processName;
|
||||
}
|
||||
|
||||
public static string ProcessNameToExe(this string processName)
|
||||
{
|
||||
return processName.ProcessNameToExe(PlatformInfo.Platform);
|
||||
}
|
||||
|
||||
public static string GetAppDataPath(this IAppFolderInfo appFolderInfo)
|
||||
{
|
||||
return appFolderInfo.AppDataFolder;
|
||||
@@ -352,9 +347,9 @@ namespace NzbDrone.Common.Extensions
|
||||
return Path.Combine(GetUpdatePackageFolder(appFolderInfo), UPDATE_CLIENT_FOLDER_NAME);
|
||||
}
|
||||
|
||||
public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo, PlatformType runtime)
|
||||
public static string GetUpdateClientExePath(this IAppFolderInfo appFolderInfo)
|
||||
{
|
||||
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE_NAME).ProcessNameToExe(runtime);
|
||||
return Path.Combine(GetUpdateSandboxFolder(appFolderInfo), UPDATE_CLIENT_EXE_NAME).ProcessNameToExe();
|
||||
}
|
||||
|
||||
public static string GetDatabase(this IAppFolderInfo appFolderInfo)
|
||||
|
||||
@@ -50,7 +50,10 @@ namespace NzbDrone.Common.Instrumentation
|
||||
new Regex(@"(?<=\?|&)(X-Plex-Client-Identifier|X-Plex-Token)=(?<secret>[^&=]+?)(?= |&|$)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Notifiarr
|
||||
new Regex(@"api/v[0-9]/notification/radarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
|
||||
new Regex(@"api/v[0-9]/notification/radarr/(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase),
|
||||
|
||||
// Discord
|
||||
new Regex(@"discord.com/api/webhooks/((?<secret>[\w-]+)/)?(?<secret>[\w-]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase)
|
||||
};
|
||||
|
||||
private static readonly Regex CleanseRemoteIPRegex = new Regex(@"(?:Auth-\w+(?<!Failure|Unauthorized) ip|from) (\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})", RegexOptions.Compiled);
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
[Test]
|
||||
public void should_return_default_if_no_info_is_known()
|
||||
{
|
||||
var result = Subject.Aggregate(_localMovie, null, false);
|
||||
var result = Subject.Aggregate(_localMovie, null);
|
||||
|
||||
result.Languages.Should().Contain(_movie.MovieMetadata.Value.OriginalLanguage);
|
||||
}
|
||||
@@ -83,7 +83,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
null,
|
||||
null);
|
||||
|
||||
Subject.Aggregate(_localMovie, null, false).Languages.Should().Equal(new List<Language> { Language.French });
|
||||
Subject.Aggregate(_localMovie, null).Languages.Should().Equal(new List<Language> { Language.French });
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -94,7 +94,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
null,
|
||||
null);
|
||||
|
||||
var aggregation = Subject.Aggregate(_localMovie, null, false);
|
||||
var aggregation = Subject.Aggregate(_localMovie, null);
|
||||
|
||||
aggregation.Languages.Should().Equal(new List<Language> { Language.German });
|
||||
}
|
||||
@@ -107,7 +107,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
new List<Language> { Language.Spanish },
|
||||
null);
|
||||
|
||||
Subject.Aggregate(_localMovie, null, false).Languages.Should().Equal(new List<Language> { Language.Spanish });
|
||||
Subject.Aggregate(_localMovie, null).Languages.Should().Equal(new List<Language> { Language.Spanish });
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -118,7 +118,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
new List<Language> { Language.Unknown },
|
||||
null);
|
||||
|
||||
Subject.Aggregate(_localMovie, null, false).Languages.Should().Equal(new List<Language> { Language.French, Language.German });
|
||||
Subject.Aggregate(_localMovie, null).Languages.Should().Equal(new List<Language> { Language.French, Language.German });
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -129,7 +129,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
new List<Language> { Language.Unknown },
|
||||
new List<Language> { Language.Japanese, Language.English });
|
||||
|
||||
Subject.Aggregate(_localMovie, null, false).Languages.Should().Equal(new List<Language> { Language.Japanese, Language.English });
|
||||
Subject.Aggregate(_localMovie, null).Languages.Should().Equal(new List<Language> { Language.Japanese, Language.English });
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -140,7 +140,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
new List<Language> { Language.Unknown },
|
||||
new List<Language> { Language.Unknown });
|
||||
|
||||
Subject.Aggregate(_localMovie, null, false).Languages.Should().Equal(new List<Language> { Language.French, Language.German });
|
||||
Subject.Aggregate(_localMovie, null).Languages.Should().Equal(new List<Language> { Language.French, Language.German });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
|
||||
GivenAugmenters(_fileExtensionAugmenter, nullMock);
|
||||
|
||||
var result = Subject.Aggregate(new LocalMovie(), null, false);
|
||||
var result = Subject.Aggregate(new LocalMovie(), null);
|
||||
|
||||
result.Quality.SourceDetectionSource.Should().Be(QualityDetectionSource.Extension);
|
||||
result.Quality.ResolutionDetectionSource.Should().Be(QualityDetectionSource.Extension);
|
||||
@@ -72,7 +72,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
{
|
||||
GivenAugmenters(_fileExtensionAugmenter, _nameAugmenter);
|
||||
|
||||
var result = Subject.Aggregate(new LocalMovie(), null, false);
|
||||
var result = Subject.Aggregate(new LocalMovie(), null);
|
||||
|
||||
result.Quality.SourceDetectionSource.Should().Be(QualityDetectionSource.Name);
|
||||
result.Quality.ResolutionDetectionSource.Should().Be(QualityDetectionSource.Name);
|
||||
@@ -84,7 +84,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
{
|
||||
GivenAugmenters(_fileExtensionAugmenter, _mediaInfoAugmenter);
|
||||
|
||||
var result = Subject.Aggregate(new LocalMovie(), null, false);
|
||||
var result = Subject.Aggregate(new LocalMovie(), null);
|
||||
|
||||
result.Quality.SourceDetectionSource.Should().Be(QualityDetectionSource.Extension);
|
||||
result.Quality.ResolutionDetectionSource.Should().Be(QualityDetectionSource.MediaInfo);
|
||||
@@ -96,7 +96,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
{
|
||||
GivenAugmenters(_nameAugmenter, _mediaInfoAugmenter);
|
||||
|
||||
var result = Subject.Aggregate(new LocalMovie(), null, false);
|
||||
var result = Subject.Aggregate(new LocalMovie(), null);
|
||||
|
||||
result.Quality.SourceDetectionSource.Should().Be(QualityDetectionSource.Name);
|
||||
result.Quality.ResolutionDetectionSource.Should().Be(QualityDetectionSource.MediaInfo);
|
||||
@@ -108,7 +108,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
{
|
||||
GivenAugmenters(_nameAugmenter, _releaseNameAugmenter);
|
||||
|
||||
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem(), false);
|
||||
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem());
|
||||
|
||||
result.Quality.SourceDetectionSource.Should().Be(QualityDetectionSource.Name);
|
||||
result.Quality.ResolutionDetectionSource.Should().Be(QualityDetectionSource.Name);
|
||||
@@ -120,7 +120,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
{
|
||||
GivenAugmenters(_nameAugmenter, _releaseNameAugmenter);
|
||||
|
||||
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem(), false);
|
||||
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem());
|
||||
|
||||
result.Quality.Revision.Version.Should().Be(1);
|
||||
result.Quality.RevisionDetectionSource.Should().Be(QualityDetectionSource.Unknown);
|
||||
@@ -134,7 +134,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
|
||||
GivenAugmenters(_nameAugmenter, _releaseNameAugmenter);
|
||||
|
||||
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem(), false);
|
||||
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem());
|
||||
|
||||
result.Quality.Revision.Version.Should().Be(2);
|
||||
result.Quality.RevisionDetectionSource.Should().Be(QualityDetectionSource.Name);
|
||||
@@ -148,7 +148,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
|
||||
GivenAugmenters(_nameAugmenter, _releaseNameAugmenter);
|
||||
|
||||
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem(), false);
|
||||
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem());
|
||||
|
||||
result.Quality.Revision.Version.Should().Be(0);
|
||||
result.Quality.RevisionDetectionSource.Should().Be(QualityDetectionSource.Name);
|
||||
@@ -165,7 +165,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
|
||||
GivenAugmenters(_nameAugmenter, _releaseNameAugmenter);
|
||||
|
||||
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem(), false);
|
||||
var result = Subject.Aggregate(new LocalMovie(), new DownloadClientItem());
|
||||
|
||||
result.Quality.Revision.Version.Should().Be(2);
|
||||
result.Quality.RevisionDetectionSource.Should().Be(QualityDetectionSource.Name);
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
Movie = _movie
|
||||
};
|
||||
|
||||
Subject.Aggregate(localMovie, null, false);
|
||||
Subject.Aggregate(localMovie, null);
|
||||
|
||||
localMovie.ReleaseGroup.Should().Be("Viva");
|
||||
}
|
||||
@@ -55,7 +55,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
Movie = _movie
|
||||
};
|
||||
|
||||
Subject.Aggregate(localMovie, null, false);
|
||||
Subject.Aggregate(localMovie, null);
|
||||
|
||||
localMovie.ReleaseGroup.Should().Be("Drone");
|
||||
}
|
||||
@@ -75,7 +75,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
Movie = _movie
|
||||
};
|
||||
|
||||
Subject.Aggregate(localMovie, null, false);
|
||||
Subject.Aggregate(localMovie, null);
|
||||
|
||||
localMovie.ReleaseGroup.Should().Be("Wizzy");
|
||||
}
|
||||
@@ -95,7 +95,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
Movie = _movie
|
||||
};
|
||||
|
||||
Subject.Aggregate(localMovie, null, false);
|
||||
Subject.Aggregate(localMovie, null);
|
||||
|
||||
localMovie.ReleaseGroup.Should().Be("FraMeSToR");
|
||||
}
|
||||
@@ -115,7 +115,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
Movie = _movie
|
||||
};
|
||||
|
||||
Subject.Aggregate(localMovie, null, false);
|
||||
Subject.Aggregate(localMovie, null);
|
||||
|
||||
localMovie.ReleaseGroup.Should().Be("FraMeSToR");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.MovieImport;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Profiles;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
{
|
||||
[TestFixture]
|
||||
public class GetSceneNameFixture : CoreTest
|
||||
{
|
||||
private LocalMovie _localMovie;
|
||||
private string _movieName = "movie.title.2022.dvdrip.x264-ingot";
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
var movie = Builder<Movie>.CreateNew()
|
||||
.With(e => e.Profile = new Profile { Items = Qualities.QualityFixture.GetDefaultQualities() })
|
||||
.With(s => s.Path = @"C:\Test\Movies\Movie Title".AsOsAgnostic())
|
||||
.Build();
|
||||
|
||||
_localMovie = new LocalMovie
|
||||
{
|
||||
Movie = movie,
|
||||
Path = Path.Combine(movie.Path, "Movie Title - 2022 - Episode Title.mkv"),
|
||||
Quality = new QualityModel(Quality.Bluray720p),
|
||||
ReleaseGroup = "DRONE"
|
||||
};
|
||||
}
|
||||
|
||||
private void GivenExistingFileOnDisk()
|
||||
{
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(s => s.GetFilesWithRelativePath(It.IsAny<int>(), It.IsAny<string>()))
|
||||
.Returns(new List<MovieFile>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_download_client_item_title_as_scene_name()
|
||||
{
|
||||
_localMovie.DownloadClientMovieInfo = new ParsedMovieInfo
|
||||
{
|
||||
ReleaseTitle = _movieName
|
||||
};
|
||||
|
||||
SceneNameCalculator.GetSceneName(_localMovie).Should()
|
||||
.Be(_movieName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_use_download_client_item_title_as_scene_name_if_there_are_other_video_files()
|
||||
{
|
||||
_localMovie.OtherVideoFiles = true;
|
||||
_localMovie.DownloadClientMovieInfo = new ParsedMovieInfo
|
||||
{
|
||||
ReleaseTitle = _movieName
|
||||
};
|
||||
|
||||
_localMovie.Path = Path.Combine(@"C:\Test\Unsorted Movies", _movieName)
|
||||
.AsOsAgnostic();
|
||||
|
||||
SceneNameCalculator.GetSceneName(_localMovie).Should()
|
||||
.BeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_file_name_as_scenename_only_if_it_looks_like_scenename()
|
||||
{
|
||||
_localMovie.Path = Path.Combine(@"C:\Test\Unsorted Movies", _movieName + ".mkv")
|
||||
.AsOsAgnostic();
|
||||
|
||||
SceneNameCalculator.GetSceneName(_localMovie).Should()
|
||||
.Be(_movieName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_use_file_name_as_scenename_if_it_doesnt_look_like_scenename()
|
||||
{
|
||||
_localMovie.Path = Path.Combine(@"C:\Test\Unsorted Movies", _movieName, "aaaaa.mkv")
|
||||
.AsOsAgnostic();
|
||||
|
||||
SceneNameCalculator.GetSceneName(_localMovie).Should()
|
||||
.BeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_folder_name_as_scenename_only_if_it_looks_like_scenename()
|
||||
{
|
||||
_localMovie.FolderMovieInfo = new ParsedMovieInfo
|
||||
{
|
||||
ReleaseTitle = _movieName
|
||||
};
|
||||
|
||||
SceneNameCalculator.GetSceneName(_localMovie).Should()
|
||||
.Be(_movieName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_use_folder_name_as_scenename_if_it_doesnt_look_like_scenename()
|
||||
{
|
||||
_localMovie.Path = Path.Combine(@"C:\Test\Unsorted Movies", _movieName, "aaaaa.mkv")
|
||||
.AsOsAgnostic();
|
||||
|
||||
_localMovie.FolderMovieInfo = new ParsedMovieInfo
|
||||
{
|
||||
ReleaseTitle = "aaaaa"
|
||||
};
|
||||
|
||||
SceneNameCalculator.GetSceneName(_localMovie).Should()
|
||||
.BeNull();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_use_folder_name_as_scenename_if_there_are_other_video_files()
|
||||
{
|
||||
_localMovie.OtherVideoFiles = true;
|
||||
_localMovie.Path = Path.Combine(@"C:\Test\Unsorted Movies", _movieName, "aaaaa.mkv")
|
||||
.AsOsAgnostic();
|
||||
|
||||
_localMovie.FolderMovieInfo = new ParsedMovieInfo
|
||||
{
|
||||
ReleaseTitle = _movieName
|
||||
};
|
||||
|
||||
SceneNameCalculator.GetSceneName(_localMovie).Should()
|
||||
.BeNull();
|
||||
}
|
||||
|
||||
[TestCase(".mkv")]
|
||||
[TestCase(".par2")]
|
||||
[TestCase(".nzb")]
|
||||
public void should_remove_extension_from_nzb_title_for_scene_name(string extension)
|
||||
{
|
||||
_localMovie.DownloadClientMovieInfo = new ParsedMovieInfo
|
||||
{
|
||||
ReleaseTitle = _movieName + extension
|
||||
};
|
||||
|
||||
SceneNameCalculator.GetSceneName(_localMovie).Should()
|
||||
.Be(_movieName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,84 +159,6 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
Times.Never());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_nzb_title_as_scene_name()
|
||||
{
|
||||
GivenNewDownload();
|
||||
_downloadClientItem.Title = "malcolm.in.the.middle.2015.dvdrip.xvid-ingot";
|
||||
|
||||
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, _downloadClientItem);
|
||||
|
||||
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<MovieFile>(c => c.SceneName == _downloadClientItem.Title)));
|
||||
}
|
||||
|
||||
[TestCase(".mkv")]
|
||||
[TestCase(".par2")]
|
||||
[TestCase(".nzb")]
|
||||
public void should_remove_extension_from_nzb_title_for_scene_name(string extension)
|
||||
{
|
||||
GivenNewDownload();
|
||||
var title = "malcolm.in.the.middle.2015.dvdrip.xvid-ingot";
|
||||
|
||||
_downloadClientItem.Title = title + extension;
|
||||
|
||||
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true, _downloadClientItem);
|
||||
|
||||
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<MovieFile>(c => c.SceneName == title)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_file_name_as_scenename_only_if_it_looks_like_scenename()
|
||||
{
|
||||
GivenNewDownload();
|
||||
_approvedDecisions.First().LocalMovie.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), "movie.title.2018.dvdrip.xvid-ingot.mkv");
|
||||
|
||||
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
|
||||
|
||||
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<MovieFile>(c => c.SceneName == "movie.title.2018.dvdrip.xvid-ingot")));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_use_file_name_as_scenename_if_it_doesnt_looks_like_scenename()
|
||||
{
|
||||
GivenNewDownload();
|
||||
_approvedDecisions.First().LocalMovie.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), "aaaaa.mkv");
|
||||
|
||||
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
|
||||
|
||||
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<MovieFile>(c => c.SceneName == null)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_use_folder_name_as_scenename_only_if_it_looks_like_scenename()
|
||||
{
|
||||
GivenNewDownload();
|
||||
_approvedDecisions.First().LocalMovie.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), "aaaaa.mkv");
|
||||
_approvedDecisions.First().LocalMovie.FolderMovieInfo = new ParsedMovieInfo
|
||||
{
|
||||
ReleaseTitle = "movie.title.2018.dvdrip.xvid-ingot"
|
||||
};
|
||||
|
||||
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
|
||||
|
||||
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<MovieFile>(c => c.SceneName == "movie.title.2018.dvdrip.xvid-ingot")));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_not_use_folder_name_as_scenename_if_it_doesnt_looks_like_scenename()
|
||||
{
|
||||
GivenNewDownload();
|
||||
_approvedDecisions.First().LocalMovie.Path = Path.Combine(_downloadClientItem.OutputPath.ToString(), "aaaaa.mkv");
|
||||
_approvedDecisions.First().LocalMovie.FolderMovieInfo = new ParsedMovieInfo
|
||||
{
|
||||
ReleaseTitle = "aaaaa.mkv"
|
||||
};
|
||||
|
||||
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
|
||||
|
||||
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<MovieFile>(c => c.SceneName == null)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_import_larger_files_first()
|
||||
{
|
||||
@@ -411,5 +333,18 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
|
||||
Mocker.GetMock<IMediaFileService>().Verify(v => v.Add(It.Is<MovieFile>(c => c.OriginalFilePath == $"subfolder\\{name}.mkv".AsOsAgnostic())));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_include_scene_name_with_new_downloads()
|
||||
{
|
||||
var firstDecision = _approvedDecisions.First();
|
||||
firstDecision.LocalMovie.SceneName = "Movie.Title.2022.dvdrip-DRONE";
|
||||
|
||||
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, true);
|
||||
|
||||
Mocker.GetMock<IUpgradeMediaFiles>()
|
||||
.Verify(v => v.UpgradeMovieFile(It.Is<MovieFile>(e => e.SceneName == firstDecision.LocalMovie.SceneName), _approvedDecisions.First().LocalMovie, false),
|
||||
Times.Once());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,8 +102,8 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
private void GivenAugmentationSuccess()
|
||||
{
|
||||
Mocker.GetMock<IAggregationService>()
|
||||
.Setup(s => s.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>(), It.IsAny<bool>()))
|
||||
.Callback<LocalMovie, DownloadClientItem, bool>((localMovie, downloadClientItem, otherFiles) =>
|
||||
.Setup(s => s.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>()))
|
||||
.Callback<LocalMovie, DownloadClientItem>((localMovie, downloadClientItem) =>
|
||||
{
|
||||
localMovie.Movie = _localMovie.Movie;
|
||||
});
|
||||
@@ -173,7 +173,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
GivenSpecifications(_pass1);
|
||||
|
||||
Mocker.GetMock<IAggregationService>()
|
||||
.Setup(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>(), It.IsAny<bool>()))
|
||||
.Setup(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>()))
|
||||
.Throws<TestException>();
|
||||
|
||||
_videoFiles = new List<string>
|
||||
@@ -188,7 +188,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
Subject.GetImportDecisions(_videoFiles, _movie);
|
||||
|
||||
Mocker.GetMock<IAggregationService>()
|
||||
.Verify(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>(), It.IsAny<bool>()), Times.Exactly(_videoFiles.Count));
|
||||
.Verify(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>()), Times.Exactly(_videoFiles.Count));
|
||||
|
||||
ExceptionVerification.ExpectedErrors(3);
|
||||
}
|
||||
@@ -209,7 +209,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
var fileNames = _videoFiles.Select(System.IO.Path.GetFileName);
|
||||
|
||||
Mocker.GetMock<IAggregationService>()
|
||||
.Setup(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>(), It.IsAny<bool>()))
|
||||
.Setup(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>()))
|
||||
.Throws<TestException>();
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ namespace NzbDrone.Core.Test.MediaFiles.MovieImport
|
||||
public void should_return_a_decision_when_exception_is_caught()
|
||||
{
|
||||
Mocker.GetMock<IAggregationService>()
|
||||
.Setup(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>(), It.IsAny<bool>()))
|
||||
.Setup(c => c.Augment(It.IsAny<LocalMovie>(), It.IsAny<DownloadClientItem>()))
|
||||
.Throws<TestException>();
|
||||
|
||||
_videoFiles = new List<string>
|
||||
|
||||
@@ -425,7 +425,6 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
.Should().Be("South.Park.H264.DTS.[EN+ES+IT]");
|
||||
}
|
||||
|
||||
[Ignore("not currently supported")]
|
||||
[Test]
|
||||
public void should_format_mediainfo_3d_properly()
|
||||
{
|
||||
@@ -669,6 +668,27 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
.Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("eng/deu", "", "[EN+DE]")]
|
||||
[TestCase("eng/nld/deu", "", "[EN+NL+DE]")]
|
||||
[TestCase("eng/deu", ":DE", "[DE]")]
|
||||
[TestCase("eng/nld/deu", ":EN+NL", "[EN+NL]")]
|
||||
[TestCase("eng/nld/deu", ":NL+EN", "[NL+EN]")]
|
||||
[TestCase("eng/nld/deu", ":-NL", "[EN+DE]")]
|
||||
[TestCase("eng/nld/deu", ":DE+", "[DE+-]")]
|
||||
[TestCase("eng/nld/deu", ":DE+NO.", "[DE].")]
|
||||
[TestCase("eng/nld/deu", ":-EN-", "[NL+DE]-")]
|
||||
public void should_format_subtitle_languages_all(string subtitleLanguages, string format, string expected)
|
||||
{
|
||||
_movieFile.ReleaseGroup = null;
|
||||
|
||||
GivenMediaInfoModel(subtitles: subtitleLanguages);
|
||||
|
||||
_namingConfig.StandardMovieFormat = "{MediaInfo SubtitleLanguages" + format + "}End";
|
||||
|
||||
Subject.BuildFileName(_movie, _movieFile)
|
||||
.Should().Be(expected + "End");
|
||||
}
|
||||
|
||||
[TestCase(HdrFormat.None, "South.Park")]
|
||||
[TestCase(HdrFormat.Hlg10, "South.Park.HDR")]
|
||||
[TestCase(HdrFormat.Hdr10, "South.Park.HDR")]
|
||||
|
||||
23
src/NzbDrone.Core.Test/ParserTests/AnimeVersionFixture.cs
Normal file
23
src/NzbDrone.Core.Test/ParserTests/AnimeVersionFixture.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.ParserTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class AnimeVersionFixture : CoreTest
|
||||
{
|
||||
[TestCase("Anime Title - 2018 - (BD 1080p HEVC FLAC) [Dual Audio] [Group]", 1)]
|
||||
[TestCase("Anime Title - 2018v2 - (BD 1080p HEVC FLAC) [Dual Audio] [Group]", 2)]
|
||||
[TestCase("Anime Title - 2018 v2 - (BD 1080p HEVC FLAC) [Dual Audio] [Group]", 2)]
|
||||
[TestCase("[SubsPlease] Anime Title - 01 (1080p) [B1F227CF]", 1)]
|
||||
[TestCase("[SubsPlease] Anime Title - 01v2 (1080p) [B1F227CF]", 2)]
|
||||
[TestCase("[SubsPlease] Anime Title - 01 v2 (1080p) [B1F227CF]", 2)]
|
||||
public void should_be_able_to_parse_repack(string title, int version)
|
||||
{
|
||||
var result = QualityParser.ParseQuality(title);
|
||||
result.Revision.Version.Should().Be(version);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,8 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Movie Title (2020)[BDRemux AVC 1080p][E-AC3 DD Plus 5.1 Castellano-Inglés Subs]")]
|
||||
[TestCase("Movie Title (2020) [UHDRemux2160p HDR][DTS-HD MA 5.1 AC3 5.1 Castellano - True-HD 7.1 Atmos Inglés Subs]")]
|
||||
[TestCase("Movie Title (2016) [UHDRemux 2160p SDR] [Castellano DD 5.1 - Inglés DTS-HD MA 5.1 Subs]")]
|
||||
[TestCase("Movie Title 2022 [HDTV 720p][Cap.101][AC3 5.1 Castellano][www.pctnew.ORG]")]
|
||||
[TestCase("Movie Title 2022 [HDTV 720p][Cap.206][AC3 5.1 Español Castellano]")]
|
||||
public void should_parse_language_spanish(string postTitle)
|
||||
{
|
||||
var result = Parser.Parser.ParseMovieTitle(postTitle, true);
|
||||
|
||||
@@ -37,16 +37,13 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("or")]
|
||||
[TestCase("an")]
|
||||
[TestCase("of")]
|
||||
public void should_remove_common_words(string word)
|
||||
public void should_remove_common_words_from_middle_of_title(string word)
|
||||
{
|
||||
var dirtyFormat = new[]
|
||||
{
|
||||
"word.{0}.word",
|
||||
"word {0} word",
|
||||
"word-{0}-word",
|
||||
"word.word.{0}",
|
||||
"word-word-{0}",
|
||||
"word-word {0}",
|
||||
"word-{0}-word"
|
||||
};
|
||||
|
||||
foreach (var s in dirtyFormat)
|
||||
@@ -56,6 +53,27 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase("the")]
|
||||
[TestCase("and")]
|
||||
[TestCase("or")]
|
||||
[TestCase("an")]
|
||||
[TestCase("of")]
|
||||
public void should_not_remove_common_words_from_end_of_title(string word)
|
||||
{
|
||||
var dirtyFormat = new[]
|
||||
{
|
||||
"word.word.{0}",
|
||||
"word-word-{0}",
|
||||
"word-word {0}"
|
||||
};
|
||||
|
||||
foreach (var s in dirtyFormat)
|
||||
{
|
||||
var dirty = string.Format(s, word);
|
||||
dirty.CleanMovieTitle().Should().Be("wordword" + word.ToLower());
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_remove_a_from_middle_of_title()
|
||||
{
|
||||
|
||||
@@ -198,6 +198,8 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Movie Title (2020) MULTi WEB 1080p x264-JiHEFF (S:317/L:28)", false)]
|
||||
[TestCase("Movie.Titles.2020.1080p.NF.WEB.DD2.0.x264-SNEAkY", false)]
|
||||
[TestCase("The.Movie.2022.NORDiC.1080p.DV.HDR.WEB.H 265-NiDHUG", false)]
|
||||
[TestCase("Movie Title 2018 [WEB 1080p HEVC Opus] [Netaro]", false)]
|
||||
[TestCase("Movie Title 2018 (WEB 1080p HEVC Opus) [Netaro]", false)]
|
||||
public void should_parse_webdl1080p_quality(string title, bool proper)
|
||||
{
|
||||
ParseAndVerifyQuality(title, Source.WEBDL, proper, Resolution.R1080p);
|
||||
@@ -206,6 +208,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Movie.Name.S04E01.iNTERNAL.1080p.WEBRip.x264-QRUS", false)]
|
||||
[TestCase("Movie.Name.1x04.ITA.1080p.WEBMux.x264-NovaRip", false)]
|
||||
[TestCase("Movie.Name.2019.S02E07.Chapter.15.The.Believer.4Kto1080p.DSNYP.Webrip.x265.10bit.EAC3.5.1.Atmos.GokiTAoE", false)]
|
||||
[TestCase("Movie.Title.2019.1080p.AMZN.WEB-Rip.DDP.5.1.HEVC", false)]
|
||||
public void should_parse_webrip1080p_quality(string title, bool proper)
|
||||
{
|
||||
ParseAndVerifyQuality(title, Source.WEBRIP, proper, Resolution.R1080p);
|
||||
@@ -314,6 +317,8 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Movie.Title.M.2008.USA.BluRay.Remux.1080p.MPEG-2.DD.5.1-TDD")]
|
||||
[TestCase("Movie.Title.2018.1080p.BluRay.REMUX.MPEG-2.DTS-HD.MA.5.1-EPSiLON")]
|
||||
[TestCase("Movie.Title.II.2003.4K.BluRay.Remux.1080p.AVC.DTS-HD.MA.5.1-BMF")]
|
||||
[TestCase("Movie Title 2022 (BDRemux 1080p HEVC FLAC) [Netaro]")]
|
||||
[TestCase("[Vodes] Movie Title - Other Title (2020) [BDRemux 1080p HEVC Dual-Audio]")]
|
||||
public void should_parse_remux1080p_quality(string title)
|
||||
{
|
||||
ParseAndVerifyQuality(title, Source.BLURAY, false, Resolution.R1080p, Modifier.REMUX);
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace NzbDrone.Core.Test.ParserTests
|
||||
[TestCase("Some.Really.Bad.Movie.Title.[2021].1080p.WEB-HDRip.Dual.Audio.[Hindi.[Clean]. .English].x264.AAC.DD.2.0.By.Full4Movies.mkv-xpost", null)]
|
||||
[TestCase("The.Movie.Title.2013.1080p.10bit.AMZN.WEB-DL.DDP5.1.HEVC-Vyndros", "Vyndros")]
|
||||
[TestCase("Movie.Name.2022.1080p.BluRay.x264-[YTS.AG]", "YTS.AG")]
|
||||
[TestCase("Movie.Title.2019.1080p.AMZN.WEB-Rip.DDP.5.1.HEVC", null)]
|
||||
public void should_parse_expected_release_group(string title, string expected)
|
||||
{
|
||||
Parser.Parser.ParseReleaseGroup(title).Should().Be(expected);
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||
.Returns(true);
|
||||
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Radarr.Update.exe"))))
|
||||
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Radarr.Update".ProcessNameToExe()))))
|
||||
.Returns(true);
|
||||
|
||||
_sandboxFolder = Mocker.GetMock<IAppFolderInfo>().Object.GetUpdateSandboxFolder();
|
||||
@@ -165,7 +165,7 @@ namespace NzbDrone.Core.Test.UpdateTests
|
||||
public void should_return_with_warning_if_updater_doesnt_exists()
|
||||
{
|
||||
Mocker.GetMock<IDiskProvider>()
|
||||
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Radarr.Update.exe"))))
|
||||
.Setup(v => v.FileExists(It.Is<string>(s => s.EndsWith("Radarr.Update".ProcessNameToExe()))))
|
||||
.Returns(false);
|
||||
|
||||
Subject.Execute(new ApplicationUpdateCommand());
|
||||
|
||||
@@ -70,6 +70,7 @@ namespace NzbDrone.Core.DecisionEngine
|
||||
{
|
||||
DownloadDecision decision = null;
|
||||
_logger.ProgressTrace("Processing release {0}/{1}", reportNumber, reports.Count);
|
||||
_logger.Debug("Processing release '{0}' from '{1}'", report.Title, report.Indexer);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -110,7 +110,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
|
||||
public override string Name => "rTorrent";
|
||||
|
||||
public override ProviderMessage Message => new ProviderMessage($"Radarr will handle automatic removal of torrents based on the current seed criteria in Settings->Indexers. After importing it will also set \"{_imported_view}\" as an rTorrent view, which can be used in rTorrent scripts to customize behavior.", ProviderMessageType.Info);
|
||||
public override ProviderMessage Message => new ProviderMessage($"rTorrent will not pause torrents when they meet the seed criteria. Radarr will handle automatic removal of torrents based on the current seed criteria in Settings->Indexers only when Remove Completed is enabled. After importing it will also set \"{_imported_view}\" as an rTorrent view, which can be used in rTorrent scripts to customize behavior.", ProviderMessageType.Info);
|
||||
|
||||
public override IEnumerable<DownloadClientItem> GetItems()
|
||||
{
|
||||
|
||||
@@ -37,10 +37,10 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
[FieldDefinition(1, Label = "Port", Type = FieldType.Textbox)]
|
||||
public int Port { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to ruTorrent")]
|
||||
[FieldDefinition(2, Label = "Use SSL", Type = FieldType.Checkbox, HelpText = "Use secure connection when connecting to rTorrent")]
|
||||
public bool UseSsl { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "Url Path", Type = FieldType.Textbox, HelpText = "Path to the XMLRPC endpoint, see http(s)://[host]:[port]/[urlPath]. When using ruTorrent this usually is RPC2 or (path to ruTorrent)/plugins/rpc/rpc.php")]
|
||||
[FieldDefinition(3, Label = "Url Path", Type = FieldType.Textbox, HelpText = "Path to the XMLRPC endpoint, see http(s)://[host]:[port]/[urlPath]. This is usually RPC2 or [path to ruTorrent]/plugins/rpc/rpc.php when using ruTorrent.")]
|
||||
public string UrlBase { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "Username", Type = FieldType.Textbox, Privacy = PrivacyLevel.UserName)]
|
||||
@@ -64,7 +64,7 @@ namespace NzbDrone.Core.Download.Clients.RTorrent
|
||||
[FieldDefinition(10, Label = "Older Priority", Type = FieldType.Select, SelectOptions = typeof(RTorrentPriority), HelpText = "Priority to use when grabbing movies that were released over 14 days ago")]
|
||||
public int OlderMoviePriority { get; set; }
|
||||
|
||||
[FieldDefinition(11, Label = "Add Stopped", Type = FieldType.Checkbox, HelpText = "Enabling will add torrents and magnets to ruTorrent in a stopped state. This may break magnet files.")]
|
||||
[FieldDefinition(11, Label = "Add Stopped", Type = FieldType.Checkbox, HelpText = "Enabling will add torrents and magnets to rTorrent in a stopped state. This may break magnet files.")]
|
||||
public bool AddStopped { get; set; }
|
||||
|
||||
public NzbDroneValidationResult Validate()
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace NzbDrone.Core.Download
|
||||
movieGrabbedEvent.DownloadId = downloadClientId;
|
||||
}
|
||||
|
||||
_logger.ProgressInfo("Report sent to {0}. {1}", downloadClient.Definition.Name, downloadTitle);
|
||||
_logger.ProgressInfo("Report sent to {0} from indexer {1}. {2}", downloadClient.Definition.Name, remoteMovie.Release.Indexer, downloadTitle);
|
||||
_eventAggregator.PublishEvent(movieGrabbedEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace NzbDrone.Core.HealthCheck
|
||||
.AddQueryParam("version", BuildInfo.Version)
|
||||
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
|
||||
.AddQueryParam("arch", RuntimeInformation.OSArchitecture)
|
||||
.AddQueryParam("runtime", PlatformInfo.Platform.ToString().ToLowerInvariant())
|
||||
.AddQueryParam("runtime", "netcore")
|
||||
.AddQueryParam("branch", _configFileProvider.Branch)
|
||||
.Build();
|
||||
try
|
||||
|
||||
@@ -152,6 +152,7 @@ namespace NzbDrone.Core.History
|
||||
history.Data.Add("Guid", message.Movie.Release.Guid);
|
||||
history.Data.Add("TmdbId", message.Movie.Release.TmdbId.ToString());
|
||||
history.Data.Add("Protocol", ((int)message.Movie.Release.DownloadProtocol).ToString());
|
||||
history.Data.Add("CustomFormatScore", message.Movie.CustomFormatScore.ToString());
|
||||
history.Data.Add("IndexerFlags", message.Movie.Release.IndexerFlags.ToString());
|
||||
history.Data.Add("IndexerId", message.Movie.Release.IndexerId.ToString());
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.ImportLists.Exceptions;
|
||||
using NzbDrone.Core.ImportLists.ImportListMovies;
|
||||
using NzbDrone.Core.Notifications.Plex.PlexTv;
|
||||
using NzbDrone.Core.Notifications.Plex.Server;
|
||||
|
||||
namespace NzbDrone.Core.ImportLists.Plex
|
||||
@@ -19,7 +20,7 @@ namespace NzbDrone.Core.ImportLists.Plex
|
||||
|
||||
public virtual IList<ImportListMovie> ParseResponse(ImportListResponse importResponse)
|
||||
{
|
||||
List<PlexSectionItem> items;
|
||||
List<PlexWatchlistItem> items;
|
||||
|
||||
_importResponse = importResponse;
|
||||
|
||||
@@ -30,7 +31,7 @@ namespace NzbDrone.Core.ImportLists.Plex
|
||||
return movies;
|
||||
}
|
||||
|
||||
items = Json.Deserialize<PlexResponse<PlexSectionResponse>>(_importResponse.Content)
|
||||
items = Json.Deserialize<PlexResponse<PlexWatchlistRespone>>(_importResponse.Content)
|
||||
.MediaContainer
|
||||
.Items;
|
||||
|
||||
|
||||
@@ -702,6 +702,7 @@
|
||||
"PreferIndexerFlags": "Prefer Indexer Flags",
|
||||
"PreferIndexerFlagsHelpText": "Prioritize releases with special flags",
|
||||
"Preferred": "Preferred",
|
||||
"PreferredProtocol": "Preferred Protocol",
|
||||
"PreferredSize": "Preferred Size",
|
||||
"PreferTorrent": "Prefer Torrent",
|
||||
"PreferUsenet": "Prefer Usenet",
|
||||
@@ -729,7 +730,7 @@
|
||||
"PtpOldSettingsCheckMessage": "The following PassThePopcorn indexers have deprecated settings and should be updated: {0}",
|
||||
"PublishedDate": "Published Date",
|
||||
"Qualities": "Qualities",
|
||||
"QualitiesHelpText": "Qualities higher in the list are more preferred. Qualities within the same group are equal. Only checked qualities are wanted",
|
||||
"QualitiesHelpText": "Qualities higher in the list are more preferred even if not checked. Qualities within the same group are equal. Only checked qualities are wanted",
|
||||
"Quality": "Quality",
|
||||
"QualityCutoffHasNotBeenMet": "Quality cutoff has not been met",
|
||||
"QualityDefinitions": "Quality Definitions",
|
||||
|
||||
@@ -1145,5 +1145,6 @@
|
||||
"RottenTomatoesRating": "Tomaattiarvio",
|
||||
"TotalMovies": "Elokuvia yhteensä",
|
||||
"ApplicationURL": "Sovelluksen URL-osoite",
|
||||
"ApplicationUrlHelpText": "Sovelluksen ulkoinen URL-osoite, johon sisältyy http(s)://, portti ja URL-perusta."
|
||||
"ApplicationUrlHelpText": "Sovelluksen ulkoinen URL-osoite, johon sisältyy http(s)://, portti ja URL-perusta.",
|
||||
"PreferredProtocol": "Ensisijainen protokolla"
|
||||
}
|
||||
|
||||
@@ -1145,5 +1145,6 @@
|
||||
"RottenTomatoesRating": "Tomato Értékelés",
|
||||
"TotalMovies": "Összes film",
|
||||
"ApplicationUrlHelpText": "Az alkalmazás külső URL-címe, beleértve a \"http(s)://\"-t, a \"Portot\" és az \"URL-alapot\" is",
|
||||
"ApplicationURL": "Alkalmazás URL-je"
|
||||
"ApplicationURL": "Alkalmazás URL-je",
|
||||
"PreferredProtocol": "Preferált protokoll"
|
||||
}
|
||||
|
||||
@@ -79,7 +79,6 @@
|
||||
"BranchUpdate": "更新Radarr的分支",
|
||||
"Branch": "分支",
|
||||
"Calendar": "日历",
|
||||
"BindAddressHelpText": "有效的 IP4 地址或以'*'代表所有地址",
|
||||
"BackupRetentionHelpText": "早于保留周期的自动备份将被自动清除",
|
||||
"BackupNow": "马上备份",
|
||||
"BackupIntervalHelpText": "自动备份时间间隔",
|
||||
@@ -196,7 +195,7 @@
|
||||
"Result": "结果",
|
||||
"RestoreBackup": "恢复备份",
|
||||
"Restore": "恢复",
|
||||
"RestartRequiredHelpTextWarning": "重启生效",
|
||||
"RestartRequiredHelpTextWarning": "需要重新启动才能生效",
|
||||
"RestartRadarr": "重启Radarr",
|
||||
"RestartNow": "马上重启",
|
||||
"Restart": "重启",
|
||||
@@ -637,7 +636,7 @@
|
||||
"InstallLatest": "安装最新版",
|
||||
"IndexerStatusCheckSingleClientMessage": "搜刮器因错误不可用:{0}",
|
||||
"IndexerStatusCheckAllClientMessage": "所有搜刮器都因错误不可用",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "没有任何索引器勾选了手动搜索,因此Radarr 不会提供任何手动搜索的结果",
|
||||
"IndexerSearchCheckNoInteractiveMessage": "没有任何索引器开启了手动搜索,因此Radarr 不会提供任何手动搜索的结果",
|
||||
"IndexerRssHealthCheckNoIndexers": "没有任何索引器开启了RSS同步,Radarr不会自动抓取新发布的影片",
|
||||
"IndexerLongTermStatusCheckSingleClientMessage": "由于故障6小时,下列索引器都已不可用:{0}",
|
||||
"IndexerLongTermStatusCheckAllClientMessage": "由于故障超过6小时,所有索引器均不可用",
|
||||
@@ -1145,5 +1144,7 @@
|
||||
"OnMovieAddedHelpText": "电影添加时",
|
||||
"TotalMovies": "电影总数",
|
||||
"ApplicationUrlHelpText": "此应用的外部URL包含 http(s)://,端口和基本URL",
|
||||
"ApplicationURL": "程序URL"
|
||||
"ApplicationURL": "程序URL",
|
||||
"BindAddressHelpText": "有效的 IPv4 地址或以'*'代表所有接口",
|
||||
"PreferredProtocol": "首选协议"
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||
mediaInfoModel.VideoCodecID = analysis.PrimaryVideoStream?.CodecTagString;
|
||||
mediaInfoModel.VideoProfile = analysis.PrimaryVideoStream?.Profile;
|
||||
mediaInfoModel.VideoBitrate = analysis.PrimaryVideoStream?.BitRate ?? 0;
|
||||
mediaInfoModel.VideoMultiViewCount = 1;
|
||||
mediaInfoModel.VideoMultiViewCount = analysis.PrimaryVideoStream?.Tags.ContainsKey("stereo_mode") ?? false ? 2 : 1;
|
||||
mediaInfoModel.VideoBitDepth = GetPixelFormat(analysis.PrimaryVideoStream?.PixelFormat)?.Components.Min(x => x.BitDepth) ?? 8;
|
||||
mediaInfoModel.VideoColourPrimaries = analysis.PrimaryVideoStream?.ColorPrimaries;
|
||||
mediaInfoModel.VideoTransferCharacteristics = analysis.PrimaryVideoStream?.ColorTransfer;
|
||||
|
||||
@@ -41,7 +41,12 @@ namespace NzbDrone.Core.MediaFiles
|
||||
|
||||
if (RelativePath.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return System.IO.Path.GetFileName(RelativePath);
|
||||
return System.IO.Path.GetFileNameWithoutExtension(RelativePath);
|
||||
}
|
||||
|
||||
if (Path.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return System.IO.Path.GetFileNameWithoutExtension(Path);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation
|
||||
{
|
||||
public interface IAggregationService
|
||||
{
|
||||
LocalMovie Augment(LocalMovie localMovie, DownloadClientItem downloadClientItem, bool otherFiles);
|
||||
LocalMovie Augment(LocalMovie localMovie, DownloadClientItem downloadClientItem);
|
||||
}
|
||||
|
||||
public class AggregationService : IAggregationService
|
||||
@@ -37,7 +37,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public LocalMovie Augment(LocalMovie localMovie, DownloadClientItem downloadClientItem, bool otherFiles)
|
||||
public LocalMovie Augment(LocalMovie localMovie, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
var isMediaFile = MediaFileExtensions.Extensions.Contains(Path.GetExtension(localMovie.Path));
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation
|
||||
}
|
||||
|
||||
localMovie.Size = _diskProvider.GetFileSize(localMovie.Path);
|
||||
localMovie.SceneName = localMovie.SceneSource ? SceneNameCalculator.GetSceneName(localMovie) : null;
|
||||
|
||||
if (isMediaFile && (!localMovie.ExistingFile || _configService.EnableMediaInfo))
|
||||
{
|
||||
@@ -62,7 +63,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation
|
||||
{
|
||||
try
|
||||
{
|
||||
augmenter.Aggregate(localMovie, downloadClientItem, otherFiles);
|
||||
augmenter.Aggregate(localMovie, downloadClientItem);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
{
|
||||
public class AggregateEdition : IAggregateLocalMovie
|
||||
{
|
||||
public LocalMovie Aggregate(LocalMovie localMovie, DownloadClientItem downloadClientItem, bool otherFiles)
|
||||
public LocalMovie Aggregate(LocalMovie localMovie, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
var movieEdition = localMovie.DownloadClientMovieInfo?.Edition;
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public LocalMovie Aggregate(LocalMovie localMovie, DownloadClientItem downloadClientItem, bool otherFiles)
|
||||
public LocalMovie Aggregate(LocalMovie localMovie, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
var languages = new List<Language> { localMovie.Movie?.MovieMetadata.Value.OriginalLanguage ?? Language.Unknown };
|
||||
var languagesConfidence = Confidence.Default;
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public LocalMovie Aggregate(LocalMovie localMovie, DownloadClientItem downloadClientItem, bool otherFiles)
|
||||
public LocalMovie Aggregate(LocalMovie localMovie, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
var source = Source.UNKNOWN;
|
||||
var sourceConfidence = Confidence.Default;
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
{
|
||||
public class AggregateReleaseGroup : IAggregateLocalMovie
|
||||
{
|
||||
public LocalMovie Aggregate(LocalMovie localMovie, DownloadClientItem downloadClientItem, bool otherFiles)
|
||||
public LocalMovie Aggregate(LocalMovie localMovie, DownloadClientItem downloadClientItem)
|
||||
{
|
||||
var releaseGroup = localMovie.DownloadClientMovieInfo?.ReleaseGroup;
|
||||
|
||||
|
||||
@@ -5,6 +5,6 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Aggregation.Aggregators
|
||||
{
|
||||
public interface IAggregateLocalMovie
|
||||
{
|
||||
LocalMovie Aggregate(LocalMovie localMovie, DownloadClientItem downloadClientItem, bool otherFiles);
|
||||
LocalMovie Aggregate(LocalMovie localMovie, DownloadClientItem downloadClientItem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,8 +118,8 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
|
||||
|
||||
if (newDownload)
|
||||
{
|
||||
movieFile.SceneName = localMovie.SceneName;
|
||||
movieFile.OriginalFilePath = GetOriginalFilePath(downloadClientItem, localMovie);
|
||||
movieFile.SceneName = GetSceneName(downloadClientItem, localMovie);
|
||||
var moveResult = _movieFileUpgrader.UpgradeMovieFile(movieFile, localMovie, copyOnly); //TODO: Check if this works
|
||||
oldFiles = moveResult.OldFiles;
|
||||
}
|
||||
@@ -208,32 +208,5 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
|
||||
|
||||
return Path.Combine(Path.GetFileName(parentPath), Path.GetFileName(path));
|
||||
}
|
||||
|
||||
private string GetSceneName(DownloadClientItem downloadClientItem, LocalMovie localMovie)
|
||||
{
|
||||
if (downloadClientItem != null)
|
||||
{
|
||||
var sceneNameTitle = SceneChecker.GetSceneTitle(downloadClientItem.Title);
|
||||
if (sceneNameTitle != null)
|
||||
{
|
||||
return sceneNameTitle;
|
||||
}
|
||||
}
|
||||
|
||||
var fileName = Path.GetFileNameWithoutExtension(localMovie.Path.CleanFilePath());
|
||||
var sceneNameFile = SceneChecker.GetSceneTitle(fileName);
|
||||
if (sceneNameFile != null)
|
||||
{
|
||||
return sceneNameFile;
|
||||
}
|
||||
|
||||
var folderTitle = localMovie.FolderMovieInfo?.ReleaseTitle;
|
||||
if (folderTitle.IsNotNullOrWhiteSpace() && SceneChecker.IsSceneTitle(folderTitle))
|
||||
{
|
||||
return folderTitle;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,8 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
|
||||
FolderMovieInfo = folderInfo,
|
||||
Path = file,
|
||||
SceneSource = sceneSource,
|
||||
ExistingFile = movie.Path.IsParentPath(file)
|
||||
ExistingFile = movie.Path.IsParentPath(file),
|
||||
OtherVideoFiles = nonSampleVideoFileCount > 1
|
||||
};
|
||||
|
||||
decisions.AddIfNotNull(GetDecision(localMovie, downloadClientItem, nonSampleVideoFileCount > 1));
|
||||
@@ -124,7 +125,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport
|
||||
|
||||
try
|
||||
{
|
||||
_aggregationService.Augment(localMovie, downloadClientItem, otherFiles);
|
||||
_aggregationService.Augment(localMovie, downloadClientItem);
|
||||
|
||||
if (localMovie.Movie == null)
|
||||
{
|
||||
|
||||
@@ -341,7 +341,7 @@ namespace NzbDrone.Core.MediaFiles.MovieImport.Manual
|
||||
localMovie.SceneSource = !existingFile;
|
||||
}
|
||||
|
||||
localMovie = _aggregationService.Augment(localMovie, trackedDownload?.DownloadItem, false);
|
||||
localMovie = _aggregationService.Augment(localMovie, trackedDownload?.DownloadItem);
|
||||
|
||||
// Apply the user-chosen values.
|
||||
localMovie.Movie = movie;
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.IO;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
|
||||
namespace NzbDrone.Core.MediaFiles.MovieImport
|
||||
{
|
||||
public static class SceneNameCalculator
|
||||
{
|
||||
public static string GetSceneName(LocalMovie localMovie)
|
||||
{
|
||||
var otherVideoFiles = localMovie.OtherVideoFiles;
|
||||
var downloadClientInfo = localMovie.DownloadClientMovieInfo;
|
||||
|
||||
if (!otherVideoFiles && downloadClientInfo != null)
|
||||
{
|
||||
return Parser.Parser.RemoveFileExtension(downloadClientInfo.ReleaseTitle);
|
||||
}
|
||||
|
||||
var fileName = Path.GetFileNameWithoutExtension(localMovie.Path.CleanFilePath());
|
||||
|
||||
if (SceneChecker.IsSceneTitle(fileName))
|
||||
{
|
||||
return fileName;
|
||||
}
|
||||
|
||||
var folderTitle = localMovie.FolderMovieInfo?.ReleaseTitle;
|
||||
|
||||
if (!otherVideoFiles &&
|
||||
folderTitle.IsNotNullOrWhiteSpace() &&
|
||||
SceneChecker.IsSceneTitle(folderTitle))
|
||||
{
|
||||
return folderTitle;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,10 @@ namespace NzbDrone.Core.MediaFiles
|
||||
|
||||
_eventAggregator.PublishEvent(new MovieFileRenamedEvent(movie, movieFile, previousPath));
|
||||
}
|
||||
catch (FileAlreadyExistsException ex)
|
||||
{
|
||||
_logger.Warn("File not renamed, there is already a file at the destination: {0}", ex.Filename);
|
||||
}
|
||||
catch (SameFilenameException ex)
|
||||
{
|
||||
_logger.Debug("File not renamed, source and destination are the same: {0}", ex.Filename);
|
||||
|
||||
@@ -10,6 +10,7 @@ using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Processes;
|
||||
using NzbDrone.Core.HealthCheck;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
using NzbDrone.Core.Movies;
|
||||
using NzbDrone.Core.ThingiProvider;
|
||||
using NzbDrone.Core.Validation;
|
||||
@@ -95,11 +96,21 @@ namespace NzbDrone.Core.Notifications.CustomScript
|
||||
environmentVariables.Add("Radarr_Download_Client", message.DownloadClientInfo?.Name ?? string.Empty);
|
||||
environmentVariables.Add("Radarr_Download_Client_Type", message.DownloadClientInfo?.Type ?? string.Empty);
|
||||
environmentVariables.Add("Radarr_Download_Id", message.DownloadId ?? string.Empty);
|
||||
environmentVariables.Add("Radarr_MovieFile_MediaInfo_AudioChannels", MediaInfoFormatter.FormatAudioChannels(movieFile.MediaInfo).ToString());
|
||||
environmentVariables.Add("Radarr_MovieFile_MediaInfo_AudioCodec", MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, null));
|
||||
environmentVariables.Add("Radarr_MovieFile_MediaInfo_AudioLanguages", movieFile.MediaInfo.AudioLanguages.Distinct().ConcatToString(" / "));
|
||||
environmentVariables.Add("Radarr_MovieFile_MediaInfo_Languages", movieFile.MediaInfo.AudioLanguages.ConcatToString(" / "));
|
||||
environmentVariables.Add("Radarr_MovieFile_MediaInfo_Height", movieFile.MediaInfo.Height.ToString());
|
||||
environmentVariables.Add("Radarr_MovieFile_MediaInfo_Width", movieFile.MediaInfo.Width.ToString());
|
||||
environmentVariables.Add("Radarr_MovieFile_MediaInfo_Subtitles", movieFile.MediaInfo.Subtitles.ConcatToString(" / "));
|
||||
environmentVariables.Add("Radarr_MovieFile_MediaInfo_VideoCodec", MediaInfoFormatter.FormatVideoCodec(movieFile.MediaInfo, null));
|
||||
environmentVariables.Add("Radarr_MovieFile_MediaInfo_VideoDynamicRangeType", MediaInfoFormatter.FormatVideoDynamicRangeType(movieFile.MediaInfo));
|
||||
|
||||
if (message.OldMovieFiles.Any())
|
||||
{
|
||||
environmentVariables.Add("Radarr_DeletedRelativePaths", string.Join("|", message.OldMovieFiles.Select(e => e.RelativePath)));
|
||||
environmentVariables.Add("Radarr_DeletedPaths", string.Join("|", message.OldMovieFiles.Select(e => Path.Combine(movie.Path, e.RelativePath))));
|
||||
environmentVariables.Add("Radarr_DeletedDateAdded", string.Join("|", message.OldMovieFiles.Select(e => e.DateAdded)));
|
||||
}
|
||||
|
||||
ExecuteScript(environmentVariables);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
@@ -10,6 +11,7 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
public interface IPlexTvProxy
|
||||
{
|
||||
string GetAuthToken(string clientIdentifier, int pinId);
|
||||
bool Ping(string clientIdentifier, string authToken);
|
||||
}
|
||||
|
||||
public class PlexTvProxy : IPlexTvProxy
|
||||
@@ -38,6 +40,29 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
return response.AuthToken;
|
||||
}
|
||||
|
||||
public bool Ping(string clientIdentifier, string authToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Allows us to tell plex.tv that we're still active and tokens should not be expired.
|
||||
var request = BuildRequest(clientIdentifier);
|
||||
|
||||
request.ResourceUrl = "/api/v2/ping";
|
||||
request.AddQueryParam("X-Plex-Token", authToken);
|
||||
|
||||
ProcessRequest(request);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Catch all exceptions and log at trace, this information could be interesting in debugging, but expired tokens will be handled elsewhere.
|
||||
_logger.Trace(e, "Unable to ping plex.tv");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private HttpRequestBuilder BuildRequest(string clientIdentifier)
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder("https://plex.tv")
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Core.Configuration;
|
||||
@@ -11,7 +14,7 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
PlexTvPinUrlResponse GetPinUrl();
|
||||
PlexTvSignInUrlResponse GetSignInUrl(string callbackUrl, int pinId, string pinCode);
|
||||
string GetAuthToken(int pinId);
|
||||
|
||||
void Ping(string authToken);
|
||||
HttpRequest GetWatchlist(string authToken);
|
||||
}
|
||||
|
||||
@@ -19,11 +22,13 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
{
|
||||
private readonly IPlexTvProxy _proxy;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly ICached<bool> _cache;
|
||||
|
||||
public PlexTvService(IPlexTvProxy proxy, IConfigService configService)
|
||||
public PlexTvService(IPlexTvProxy proxy, IConfigService configService, ICacheManager cacheManager)
|
||||
{
|
||||
_proxy = proxy;
|
||||
_configService = configService;
|
||||
_cache = cacheManager.GetCache<bool>(GetType());
|
||||
}
|
||||
|
||||
public PlexTvPinUrlResponse GetPinUrl()
|
||||
@@ -83,8 +88,16 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
return authToken;
|
||||
}
|
||||
|
||||
public void Ping(string authToken)
|
||||
{
|
||||
// Ping plex.tv if we haven't done so in the last 24 hours for this auth token.
|
||||
_cache.Get(authToken, () => _proxy.Ping(_configService.PlexClientIdentifier, authToken), TimeSpan.FromHours(24));
|
||||
}
|
||||
|
||||
public HttpRequest GetWatchlist(string authToken)
|
||||
{
|
||||
Ping(authToken);
|
||||
|
||||
var clientIdentifier = _configService.PlexClientIdentifier;
|
||||
|
||||
var requestBuilder = new HttpRequestBuilder("https://metadata.provider.plex.tv/library/sections/watchlist/all")
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
{
|
||||
public class PlexSectionItemGuid
|
||||
{
|
||||
public string Id { get; set; }
|
||||
}
|
||||
|
||||
public class PlexWatchlistRespone
|
||||
{
|
||||
[JsonProperty("Metadata")]
|
||||
public List<PlexWatchlistItem> Items { get; set; }
|
||||
|
||||
public PlexWatchlistRespone()
|
||||
{
|
||||
Items = new List<PlexWatchlistItem>();
|
||||
}
|
||||
}
|
||||
|
||||
public class PlexWatchlistItem
|
||||
{
|
||||
public PlexWatchlistItem()
|
||||
{
|
||||
Guids = new List<PlexSectionItemGuid>();
|
||||
}
|
||||
|
||||
[JsonProperty("ratingKey")]
|
||||
public string Id { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public int Year { get; set; }
|
||||
|
||||
[JsonProperty("Guid")]
|
||||
public List<PlexSectionItemGuid> Guids { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -3,22 +3,14 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Plex.Server
|
||||
{
|
||||
public class PlexSectionItemGuid
|
||||
{
|
||||
public string Id { get; set; }
|
||||
}
|
||||
|
||||
public class PlexSectionItem
|
||||
{
|
||||
[JsonProperty("ratingKey")]
|
||||
public string Id { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public int Year { get; set; }
|
||||
|
||||
[JsonProperty("Guid")]
|
||||
public List<PlexSectionItemGuid> Guids { get; set; }
|
||||
public string Guid { get; set; }
|
||||
}
|
||||
|
||||
public class PlexSectionResponse
|
||||
@@ -36,5 +28,10 @@ namespace NzbDrone.Core.Notifications.Plex.Server
|
||||
{
|
||||
[JsonProperty("_children")]
|
||||
public List<PlexSectionItem> Items { get; set; }
|
||||
|
||||
public PlexSectionResponseLegacy()
|
||||
{
|
||||
Items = new List<PlexSectionItem>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,8 @@ namespace NzbDrone.Core.Notifications.Plex.Server
|
||||
|
||||
private void UpdateIfEnabled(Movie movie)
|
||||
{
|
||||
_plexTvService.Ping(Settings.AuthToken);
|
||||
|
||||
if (Settings.UpdateLibrary)
|
||||
{
|
||||
_logger.Debug("Scheduling library update for movie {0} {1}", movie.Id, movie.Title);
|
||||
@@ -77,7 +79,8 @@ namespace NzbDrone.Core.Notifications.Plex.Server
|
||||
|
||||
public override void ProcessQueue()
|
||||
{
|
||||
PlexUpdateQueue queue = _pendingMoviesCache.Find(Settings.Host);
|
||||
var queue = _pendingMoviesCache.Find(Settings.Host);
|
||||
|
||||
if (queue == null)
|
||||
{
|
||||
return;
|
||||
@@ -130,6 +133,8 @@ namespace NzbDrone.Core.Notifications.Plex.Server
|
||||
|
||||
public override ValidationResult Test()
|
||||
{
|
||||
_plexTvService.Ping(Settings.AuthToken);
|
||||
|
||||
var failures = new List<ValidationFailure>();
|
||||
|
||||
failures.AddIfNotNull(_plexServerService.Test(Settings));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Webhook
|
||||
@@ -19,6 +20,8 @@ namespace NzbDrone.Core.Notifications.Webhook
|
||||
SceneName = movieFile.SceneName;
|
||||
IndexerFlags = movieFile.IndexerFlags.ToString();
|
||||
Size = movieFile.Size;
|
||||
DateAdded = movieFile.DateAdded;
|
||||
MediaInfo = new WebhookMovieFileMediaInfo(movieFile);
|
||||
}
|
||||
|
||||
public int Id { get; set; }
|
||||
@@ -30,5 +33,7 @@ namespace NzbDrone.Core.Notifications.Webhook
|
||||
public string SceneName { get; set; }
|
||||
public string IndexerFlags { get; set; }
|
||||
public long Size { get; set; }
|
||||
public DateTime DateAdded { get; set; }
|
||||
public WebhookMovieFileMediaInfo MediaInfo { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Webhook
|
||||
{
|
||||
public class WebhookMovieFileMediaInfo
|
||||
{
|
||||
public WebhookMovieFileMediaInfo()
|
||||
{
|
||||
}
|
||||
|
||||
public WebhookMovieFileMediaInfo(MovieFile movieFile)
|
||||
{
|
||||
AudioChannels = MediaInfoFormatter.FormatAudioChannels(movieFile.MediaInfo);
|
||||
AudioCodec = MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, movieFile.SceneName);
|
||||
AudioLanguages = movieFile.MediaInfo.AudioLanguages.Distinct().ToList();
|
||||
Height = movieFile.MediaInfo.Height;
|
||||
Width = movieFile.MediaInfo.Width;
|
||||
Subtitles = movieFile.MediaInfo.Subtitles.Distinct().ToList();
|
||||
VideoCodec = MediaInfoFormatter.FormatVideoCodec(movieFile.MediaInfo, movieFile.SceneName);
|
||||
VideoDynamicRange = MediaInfoFormatter.FormatVideoDynamicRange(movieFile.MediaInfo);
|
||||
VideoDynamicRangeType = MediaInfoFormatter.FormatVideoDynamicRangeType(movieFile.MediaInfo);
|
||||
}
|
||||
|
||||
public decimal AudioChannels { get; set; }
|
||||
public string AudioCodec { get; set; }
|
||||
public List<string> AudioLanguages { get; set; }
|
||||
public int Height { get; set; }
|
||||
public int Width { get; set; }
|
||||
public List<string> Subtitles { get; set; }
|
||||
public string VideoCodec { get; set; }
|
||||
public string VideoDynamicRange { get; set; }
|
||||
public string VideoDynamicRangeType { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
|
||||
@@ -17,6 +19,8 @@ namespace NzbDrone.Core.Notifications.Webhook
|
||||
ReleaseTitle = remoteMovie.Release.Title;
|
||||
Indexer = remoteMovie.Release.Indexer;
|
||||
Size = remoteMovie.Release.Size;
|
||||
CustomFormats = remoteMovie.CustomFormats?.Select(x => x.Name).ToList();
|
||||
CustomFormatScore = remoteMovie.CustomFormatScore;
|
||||
}
|
||||
|
||||
public string Quality { get; set; }
|
||||
@@ -25,5 +29,7 @@ namespace NzbDrone.Core.Notifications.Webhook
|
||||
public string ReleaseTitle { get; set; }
|
||||
public string Indexer { get; set; }
|
||||
public long Size { get; set; }
|
||||
public int CustomFormatScore { get; set; }
|
||||
public List<string> CustomFormats { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace NzbDrone.Core.Organizer
|
||||
private readonly ICustomFormatService _formatService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
private static readonly Regex TitleRegex = new Regex(@"(?<tag>\{(?:imdb-|edition-))?\{(?<prefix>[- ._\[(]*)(?<token>(?:[a-z0-9]+)(?:(?<separator>[- ._]+)(?:[a-z0-9]+))?)(?::(?<customFormat>[a-z0-9|]+))?(?<suffix>[-} ._)\]]*)\}",
|
||||
private static readonly Regex TitleRegex = new Regex(@"(?<tag>\{(?:imdb-|edition-))?\{(?<prefix>[- ._\[(]*)(?<token>(?:[a-z0-9]+)(?:(?<separator>[- ._]+)(?:[a-z0-9]+))?)(?::(?<customFormat>[a-z0-9|+-]+(?<!-)))?(?<suffix>[-} ._)\]]*)\}",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
|
||||
public static readonly Regex MovieTitleRegex = new Regex(@"(?<token>\{((?:(Movie|Original))(?<separator>[- ._])(Clean|Original)?(Title|Filename)(The)?)(?::(?<customFormat>[a-z0-9|]+))?\})",
|
||||
@@ -353,30 +353,12 @@ namespace NzbDrone.Core.Organizer
|
||||
|
||||
var sceneName = movieFile.GetSceneOrFileName();
|
||||
|
||||
var videoCodec = MediaInfoFormatter.FormatVideoCodec(movieFile.MediaInfo, sceneName);
|
||||
var audioCodec = MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, sceneName);
|
||||
var videoCodec = MediaInfoFormatter.FormatVideoCodec(movieFile.MediaInfo, sceneName) ?? string.Empty;
|
||||
var audioCodec = MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, sceneName) ?? string.Empty;
|
||||
var audioChannels = MediaInfoFormatter.FormatAudioChannels(movieFile.MediaInfo);
|
||||
var audioLanguages = movieFile.MediaInfo.AudioLanguages ?? new List<string>();
|
||||
var subtitles = movieFile.MediaInfo.Subtitles ?? new List<string>();
|
||||
|
||||
var mediaInfoAudioLanguages = GetLanguagesToken(audioLanguages);
|
||||
if (!mediaInfoAudioLanguages.IsNullOrWhiteSpace())
|
||||
{
|
||||
mediaInfoAudioLanguages = $"[{mediaInfoAudioLanguages}]";
|
||||
}
|
||||
|
||||
var mediaInfoAudioLanguagesAll = mediaInfoAudioLanguages;
|
||||
if (mediaInfoAudioLanguages == "[EN]")
|
||||
{
|
||||
mediaInfoAudioLanguages = string.Empty;
|
||||
}
|
||||
|
||||
var mediaInfoSubtitleLanguages = GetLanguagesToken(subtitles);
|
||||
if (!mediaInfoSubtitleLanguages.IsNullOrWhiteSpace())
|
||||
{
|
||||
mediaInfoSubtitleLanguages = $"[{mediaInfoSubtitleLanguages}]";
|
||||
}
|
||||
|
||||
var videoBitDepth = movieFile.MediaInfo.VideoBitDepth > 0 ? movieFile.MediaInfo.VideoBitDepth.ToString() : 8.ToString();
|
||||
var audioChannelsFormatted = audioChannels > 0 ?
|
||||
audioChannels.ToString("F1", CultureInfo.InvariantCulture) :
|
||||
@@ -391,16 +373,16 @@ namespace NzbDrone.Core.Organizer
|
||||
tokenHandlers["{MediaInfo Audio}"] = m => audioCodec;
|
||||
tokenHandlers["{MediaInfo AudioCodec}"] = m => audioCodec;
|
||||
tokenHandlers["{MediaInfo AudioChannels}"] = m => audioChannelsFormatted;
|
||||
tokenHandlers["{MediaInfo AudioLanguages}"] = m => mediaInfoAudioLanguages;
|
||||
tokenHandlers["{MediaInfo AudioLanguagesAll}"] = m => mediaInfoAudioLanguagesAll;
|
||||
tokenHandlers["{MediaInfo AudioLanguages}"] = m => GetLanguagesToken(audioLanguages, m.CustomFormat, true, true);
|
||||
tokenHandlers["{MediaInfo AudioLanguagesAll}"] = m => GetLanguagesToken(audioLanguages, m.CustomFormat, false, true);
|
||||
|
||||
tokenHandlers["{MediaInfo SubtitleLanguages}"] = m => mediaInfoSubtitleLanguages;
|
||||
tokenHandlers["{MediaInfo SubtitleLanguagesAll}"] = m => mediaInfoSubtitleLanguages;
|
||||
tokenHandlers["{MediaInfo SubtitleLanguages}"] = m => GetLanguagesToken(subtitles, m.CustomFormat, false, true);
|
||||
tokenHandlers["{MediaInfo SubtitleLanguagesAll}"] = m => GetLanguagesToken(subtitles, m.CustomFormat, false, true);
|
||||
|
||||
tokenHandlers["{MediaInfo 3D}"] = m => mediaInfo3D;
|
||||
|
||||
tokenHandlers["{MediaInfo Simple}"] = m => $"{videoCodec} {audioCodec}";
|
||||
tokenHandlers["{MediaInfo Full}"] = m => $"{videoCodec} {audioCodec}{mediaInfoAudioLanguages} {mediaInfoSubtitleLanguages}";
|
||||
tokenHandlers["{MediaInfo Full}"] = m => $"{videoCodec} {audioCodec}{GetLanguagesToken(audioLanguages, m.CustomFormat, true, true)} {GetLanguagesToken(subtitles, m.CustomFormat, false, true)}";
|
||||
|
||||
tokenHandlers[MediaInfoVideoDynamicRangeToken] =
|
||||
m => MediaInfoFormatter.FormatVideoDynamicRange(movieFile.MediaInfo);
|
||||
@@ -419,7 +401,7 @@ namespace NzbDrone.Core.Organizer
|
||||
tokenHandlers["{Custom Formats}"] = m => string.Join(" ", customFormats.Where(x => x.IncludeCustomFormatWhenRenaming));
|
||||
}
|
||||
|
||||
private string GetLanguagesToken(List<string> mediaInfoLanguages)
|
||||
private string GetLanguagesToken(List<string> mediaInfoLanguages, string filter, bool skipEnglishOnly, bool quoted)
|
||||
{
|
||||
var tokens = new List<string>();
|
||||
foreach (var item in mediaInfoLanguages)
|
||||
@@ -448,7 +430,44 @@ namespace NzbDrone.Core.Organizer
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join("+", tokens.Distinct());
|
||||
tokens = tokens.Distinct().ToList();
|
||||
|
||||
var filteredTokens = tokens;
|
||||
|
||||
// Exclude or filter
|
||||
if (filter.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
if (filter.StartsWith("-"))
|
||||
{
|
||||
filteredTokens = tokens.Except(filter.Split('-')).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
filteredTokens = filter.Split('+').Intersect(tokens).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
// Replace with wildcard (maybe too limited)
|
||||
if (filter.IsNotNullOrWhiteSpace() && filter.EndsWith("+") && filteredTokens.Count != tokens.Count)
|
||||
{
|
||||
filteredTokens.Add("--");
|
||||
}
|
||||
|
||||
if (skipEnglishOnly && filteredTokens.Count == 1 && filteredTokens.First() == "EN")
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var response = string.Join("+", filteredTokens);
|
||||
|
||||
if (quoted && response.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return $"[{response}]";
|
||||
}
|
||||
else
|
||||
{
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMediaInfoIfNeeded(string pattern, MovieFile movieFile, Movie movie)
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace NzbDrone.Core.Parser
|
||||
(?<polish>\b(?:PL\W?DUB|DUB\W?PL|LEK\W?PL|PL\W?LEK)\b)|
|
||||
(?<chinese>\[(?:CH[ST]|BIG5|GB)\]|简|繁|字幕)|
|
||||
(?<ukrainian>(?:(?:\dx)?UKR))|
|
||||
(?<spanish>\b(?:español|castellano)\b)|
|
||||
(?<latvian>\bLV\b)",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
|
||||
|
||||
@@ -55,7 +56,7 @@ namespace NzbDrone.Core.Parser
|
||||
languages.Add(Language.French);
|
||||
}
|
||||
|
||||
if (lowerTitle.Contains("spanish") || lowerTitle.Contains("castellano"))
|
||||
if (lowerTitle.Contains("spanish"))
|
||||
{
|
||||
languages.Add(Language.Spanish);
|
||||
}
|
||||
@@ -297,6 +298,11 @@ namespace NzbDrone.Core.Parser
|
||||
languages.Add(Language.Chinese);
|
||||
}
|
||||
|
||||
if (match.Groups["spanish"].Success)
|
||||
{
|
||||
languages.Add(Language.Spanish);
|
||||
}
|
||||
|
||||
if (match.Groups["ukrainian"].Success)
|
||||
{
|
||||
languages.Add(Language.Ukrainian);
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace NzbDrone.Core.Parser.Model
|
||||
public bool SceneSource { get; set; }
|
||||
public string ReleaseGroup { get; set; }
|
||||
public string Edition { get; set; }
|
||||
public string SceneName { get; set; }
|
||||
public bool OtherVideoFiles { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace NzbDrone.Core.Parser
|
||||
// Regex to unbracket alternative titles.
|
||||
private static readonly Regex BracketedAlternativeTitleRegex = new Regex(@"(.*) \([ ]*AKA[ ]+(.*)\)", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex NormalizeRegex = new Regex(@"((?:\b|_)(?<!^|[^a-zA-Z0-9_']\w[^a-zA-Z0-9_'])(a(?!$|[^a-zA-Z0-9_']\w[^a-zA-Z0-9_'])|an|the|and|or|of)(?:\b|_))|\W|_",
|
||||
private static readonly Regex NormalizeRegex = new Regex(@"((?:\b|_)(?<!^|[^a-zA-Z0-9_']\w[^a-zA-Z0-9_'])(a(?!$|[^a-zA-Z0-9_']\w[^a-zA-Z0-9_'])|an|the|and|or|of)(?!$)(?:\b|_))|\W|_",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex FileExtensionRegex = new Regex(@"\.[a-z0-9]{2,4}$",
|
||||
@@ -134,7 +134,7 @@ namespace NzbDrone.Core.Parser
|
||||
private static readonly Regex CleanQualityBracketsRegex = new Regex(@"\[[a-z0-9 ._-]+\]$",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex ReleaseGroupRegex = new Regex(@"-(?<releasegroup>[a-z0-9]+(?<part2>-[a-z0-9]+)?(?!.+?(?:480p|576p|720p|1080p|2160p)))(?<!(?:WEB-DL|Blu-Ray|480p|576p|720p|1080p|2160p|DTS-HD|DTS-X|DTS-MA|DTS-ES|-ES|-EN|-CAT|-HDRip|[ ._]\d{4}-\d{2}|-\d{2}|tmdb(id)?-(?<tmdbid>\d+)|(?<imdbid>tt\d{7,8}))(?:\k<part2>)?)(?:\b|[-._ ]|$)|[-._ ]\[(?<releasegroup>[a-z0-9]+)\]$",
|
||||
private static readonly Regex ReleaseGroupRegex = new Regex(@"-(?<releasegroup>[a-z0-9]+(?<part2>-[a-z0-9]+)?(?!.+?(?:480p|576p|720p|1080p|2160p)))(?<!(?:WEB-(DL|Rip)|Blu-Ray|480p|576p|720p|1080p|2160p|DTS-HD|DTS-X|DTS-MA|DTS-ES|-ES|-EN|-CAT|-HDRip|[ ._]\d{4}-\d{2}|-\d{2}|tmdb(id)?-(?<tmdbid>\d+)|(?<imdbid>tt\d{7,8}))(?:\k<part2>)?)(?:\b|[-._ ]|$)|[-._ ]\[(?<releasegroup>[a-z0-9]+)\]$",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static readonly Regex InvalidReleaseGroupRegex = new Regex(@"^([se]\d+|[0-9a-f]{8})$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace NzbDrone.Core.Parser
|
||||
|
||||
private static readonly Regex SourceRegex = new Regex(@"\b(?:
|
||||
(?<bluray>M?BluRay|Blu-Ray|HD.?DVD|BD(?!$)|UHDBD|UHD2BD|BDISO|BDMux|BD25|BD50|BR.?DISK)|
|
||||
(?<webdl>WEB[-_. ]DL(?:mux)?|WEBDL|AmazonHD|iTunesHD|MaxdomeHD|NetflixU?HD|WebHD|[. ]WEB[. ](?:[xh][ .]?26[45]|DDP?5[. ]1)|[. ](?-i:WEB)$|(?:\d{3,4}0p)[-. ]WEB[-. ]|[-. ]WEB[-. ]\d{3,4}0p|\b\s\/\sWEB\s\/\s\b|(?:AMZN|NF|DP)[. -]WEB[. -])|
|
||||
(?<webdl>WEB[-_. ]DL(?:mux)?|WEBDL|AmazonHD|iTunesHD|MaxdomeHD|NetflixU?HD|WebHD|[. ]WEB[. ](?:[xh][ .]?26[45]|DDP?5[. ]1)|[. ](?-i:WEB)$|(?:\d{3,4}0p)[-. ]WEB[-. ]|[-. ]WEB[-. ]\d{3,4}0p|\b\s\/\sWEB\s\/\s\b|(?:AMZN|NF|DP)[. -]WEB[. -](?!Rip))|
|
||||
(?<webrip>WebRip|Web-Rip|WEBMux)|
|
||||
(?<hdtv>HDTV)|
|
||||
(?<bdrip>BDRip|BDLight)|
|
||||
@@ -50,7 +50,7 @@ namespace NzbDrone.Core.Parser
|
||||
private static readonly Regex RepackRegex = new Regex(@"\b(?<repack>repack|rerip)\b",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex VersionRegex = new Regex(@"\dv(?<version>\d)\b|\[v(?<version>\d)\]",
|
||||
private static readonly Regex VersionRegex = new Regex(@"\d[-._ ]?v(?<version>\d)[-._ ]|\[v(?<version>\d)\]",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex RealRegex = new Regex(@"\b(?<real>REAL)\b",
|
||||
@@ -69,13 +69,11 @@ namespace NzbDrone.Core.Parser
|
||||
private static readonly Regex OtherSourceRegex = new Regex(@"(?<hdtv>HD[-_. ]TV)|(?<sdtv>SD[-_. ]TV)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex AnimeBlurayRegex = new Regex(@"bd(?:720|1080|2160)|(?<=[-_. (\[])bd(?=[-_. )\]])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex AnimeWebDlRegex = new Regex(@"\[WEB\]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex AnimeWebDlRegex = new Regex(@"\[WEB\]|[\[\(]WEB[ .]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex HighDefPdtvRegex = new Regex(@"hr[-_. ]ws", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex RemuxRegex = new Regex(@"(?:[_. \[]|\d{4}p-)(?<remux>(?:(BD|UHD)[-_. ]?)?Remux)\b", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex HDShitQualityRegex = new Regex(@"(HD-TS|HDTS|HDTSRip|HD-TC|HDTC|HDCAM|HD-CAM)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex RemuxRegex = new Regex(@"(?:[_. \[]|\d{4}p-)(?<remux>(?:(BD|UHD)[-_. ]?)?Remux)\b|(?<remux>(?:(BD|UHD)[-_. ]?)?Remux[_. ]\d{4}p)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex HardcodedSubsRegex = new Regex(@"\b(?<hcsub>(\w+(?<!(SOFT|HORRIBLE))SUBS?)\b)|(?<hc>(HC|SUBBED))\b",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<PackageReference Include="MailKit" Version="2.15.0" />
|
||||
<PackageReference Include="Npgsql" Version="6.0.3" />
|
||||
<PackageReference Include="Servarr.FFMpegCore" Version="4.7.0-26" />
|
||||
<PackageReference Include="Servarr.FFprobe" Version="5.0.1.93" />
|
||||
<PackageReference Include="Servarr.FFprobe" Version="5.1.2.103" />
|
||||
<PackageReference Include="System.Memory" Version="4.5.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||
|
||||
@@ -146,7 +146,7 @@ namespace NzbDrone.Core.Update
|
||||
_logger.Info("Preparing client");
|
||||
_diskTransferService.TransferFolder(_appFolderInfo.GetUpdateClientFolder(), updateSandboxFolder, TransferMode.Move);
|
||||
|
||||
var updateClientExePath = _appFolderInfo.GetUpdateClientExePath(updatePackage.Runtime);
|
||||
var updateClientExePath = _appFolderInfo.GetUpdateClientExePath();
|
||||
|
||||
if (!_diskProvider.FileExists(updateClientExePath))
|
||||
{
|
||||
@@ -155,7 +155,7 @@ namespace NzbDrone.Core.Update
|
||||
}
|
||||
|
||||
// Set executable flag on update app
|
||||
if (OsInfo.IsOsx || (OsInfo.IsLinux && PlatformInfo.IsNetCore))
|
||||
if (OsInfo.IsOsx || OsInfo.IsLinux)
|
||||
{
|
||||
_diskProvider.SetFilePermissions(updateClientExePath, "755", null);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace NzbDrone.Core.Update
|
||||
@@ -12,6 +12,5 @@ namespace NzbDrone.Core.Update
|
||||
public UpdateChanges Changes { get; set; }
|
||||
public string Hash { get; set; }
|
||||
public string Branch { get; set; }
|
||||
public PlatformType Runtime { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using FluentValidation.Validators;
|
||||
using NzbDrone.Common.Cloud;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Http;
|
||||
@@ -39,7 +40,7 @@ namespace NzbDrone.Core.Update
|
||||
.AddQueryParam("version", currentVersion)
|
||||
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
|
||||
.AddQueryParam("arch", RuntimeInformation.OSArchitecture)
|
||||
.AddQueryParam("runtime", PlatformInfo.Platform.ToString().ToLowerInvariant())
|
||||
.AddQueryParam("runtime", "netcore")
|
||||
.AddQueryParam("runtimeVer", _platformInfo.Version)
|
||||
.AddQueryParam("dbType", _mainDatabase.DatabaseType)
|
||||
.SetSegment("branch", branch);
|
||||
@@ -67,7 +68,7 @@ namespace NzbDrone.Core.Update
|
||||
.AddQueryParam("version", currentVersion)
|
||||
.AddQueryParam("os", OsInfo.Os.ToString().ToLowerInvariant())
|
||||
.AddQueryParam("arch", RuntimeInformation.OSArchitecture)
|
||||
.AddQueryParam("runtime", PlatformInfo.Platform.ToString().ToLowerInvariant())
|
||||
.AddQueryParam("runtime", "netcore")
|
||||
.AddQueryParam("runtimeVer", _platformInfo.Version)
|
||||
.SetSegment("branch", branch);
|
||||
|
||||
|
||||
@@ -247,9 +247,7 @@ namespace NzbDrone.Mono.Disk
|
||||
newFile.CreateSymbolicLinkTo(fullPath);
|
||||
}
|
||||
}
|
||||
else if (((PlatformInfo.Platform == PlatformType.Mono && PlatformInfo.GetVersion() >= new Version(6, 0)) ||
|
||||
PlatformInfo.Platform == PlatformType.NetCore) &&
|
||||
(!FileExists(destination) || overwrite))
|
||||
else if (!FileExists(destination) || overwrite)
|
||||
{
|
||||
TransferFilePatched(source, destination, overwrite, false);
|
||||
}
|
||||
@@ -294,14 +292,9 @@ namespace NzbDrone.Mono.Disk
|
||||
throw;
|
||||
}
|
||||
}
|
||||
else if ((PlatformInfo.Platform == PlatformType.Mono && PlatformInfo.GetVersion() >= new Version(6, 0)) ||
|
||||
PlatformInfo.Platform == PlatformType.NetCore)
|
||||
{
|
||||
TransferFilePatched(source, destination, false, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.MoveFileInternal(source, destination);
|
||||
TransferFilePatched(source, destination, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,7 +306,7 @@ namespace NzbDrone.Mono.Disk
|
||||
// Catch the exception and attempt to handle these edgecases
|
||||
|
||||
// Mono 6.x till 6.10 doesn't properly try use rename first.
|
||||
if (move && (PlatformInfo.Platform == PlatformType.NetCore))
|
||||
if (move)
|
||||
{
|
||||
if (Syscall.lstat(source, out var sourcestat) == 0 &&
|
||||
Syscall.lstat(destination, out var deststat) != 0 &&
|
||||
@@ -341,7 +334,7 @@ namespace NzbDrone.Mono.Disk
|
||||
var dstInfo = new FileInfo(destination);
|
||||
var exists = dstInfo.Exists && srcInfo.Exists;
|
||||
|
||||
if (PlatformInfo.Platform == PlatformType.NetCore && exists && dstInfo.Length == srcInfo.Length)
|
||||
if (exists && dstInfo.Length == srcInfo.Length)
|
||||
{
|
||||
// mono 6.0, mono 6.4 and netcore 3.1 bug: full length file since utime and chmod happens at the end
|
||||
_logger.Debug("{3} failed to {2} file likely due to known {3} bug, attempting to {2} directly. '{0}' -> '{1}'", source, destination, move ? "move" : "copy", PlatformInfo.PlatformName);
|
||||
|
||||
@@ -129,7 +129,7 @@ namespace NzbDrone.Update.UpdateEngine
|
||||
_diskTransferService.MirrorFolder(_appFolderInfo.GetUpdatePackageFolder(), installationFolder);
|
||||
|
||||
// Set executable flag on app and ffprobe
|
||||
if (OsInfo.IsOsx || (OsInfo.IsLinux && PlatformInfo.IsNetCore))
|
||||
if (OsInfo.IsOsx || OsInfo.IsLinux)
|
||||
{
|
||||
_diskProvider.SetFilePermissions(Path.Combine(installationFolder, "Radarr"), "755", null);
|
||||
_diskProvider.SetFilePermissions(Path.Combine(installationFolder, "ffprobe"), "755", null);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
@@ -47,7 +48,15 @@ namespace Radarr.Api.V3.CustomFormats
|
||||
|
||||
private static ICustomFormatSpecification MapSpecification(CustomFormatSpecificationSchema resource, List<ICustomFormatSpecification> specifications)
|
||||
{
|
||||
var type = specifications.SingleOrDefault(x => x.GetType().Name == resource.Implementation).GetType();
|
||||
var matchingSpec = specifications.SingleOrDefault(x => x.GetType().Name == resource.Implementation);
|
||||
|
||||
if (matchingSpec is null)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"{resource.Implementation} is not a valid specification implementation");
|
||||
}
|
||||
|
||||
var type = matchingSpec.GetType();
|
||||
var spec = (ICustomFormatSpecification)SchemaBuilder.ReadFromSchema(resource.Fields, type);
|
||||
spec.Name = resource.Name;
|
||||
spec.Negate = resource.Negate;
|
||||
|
||||
@@ -122,9 +122,8 @@ namespace Radarr.Api.V3.Indexers
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Movie search failed: " + ex.Message);
|
||||
throw new NzbDroneClientException(HttpStatusCode.InternalServerError, ex.Message);
|
||||
}
|
||||
|
||||
return new List<ReleaseResource>();
|
||||
}
|
||||
|
||||
private List<ReleaseResource> GetRss()
|
||||
|
||||
@@ -183,7 +183,7 @@ namespace Radarr.Api.V3.Queue
|
||||
case "status":
|
||||
return q => q.Status;
|
||||
case "movies.sortTitle":
|
||||
return q => q.Movie?.MovieMetadata.Value.SortTitle ?? string.Empty;
|
||||
return q => q.Movie?.MovieMetadata.Value.SortTitle ?? q.Title;
|
||||
case "title":
|
||||
return q => q.Title;
|
||||
case "languages":
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace Radarr.Api.V3.System
|
||||
AppData = _appFolderInfo.GetAppDataPath(),
|
||||
OsName = _osInfo.Name,
|
||||
OsVersion = _osInfo.Version,
|
||||
IsNetCore = PlatformInfo.IsNetCore,
|
||||
IsNetCore = true,
|
||||
IsLinux = OsInfo.IsLinux,
|
||||
IsOsx = OsInfo.IsOsx,
|
||||
IsWindows = OsInfo.IsWindows,
|
||||
@@ -83,7 +83,7 @@ namespace Radarr.Api.V3.System
|
||||
MigrationVersion = _database.Migration,
|
||||
UrlBase = _configFileProvider.UrlBase,
|
||||
RuntimeVersion = _platformInfo.Version,
|
||||
RuntimeName = PlatformInfo.Platform,
|
||||
RuntimeName = "netcore",
|
||||
StartTime = _runtimeInfo.StartTime,
|
||||
PackageVersion = _deploymentInfoProvider.PackageVersion,
|
||||
PackageAuthor = _deploymentInfoProvider.PackageAuthor,
|
||||
|
||||
@@ -4744,10 +4744,10 @@ mobile-detect@1.4.5:
|
||||
resolved "https://registry.yarnpkg.com/mobile-detect/-/mobile-detect-1.4.5.tgz#da393c3c413ca1a9bcdd9ced653c38281c0fb6ad"
|
||||
integrity sha512-yc0LhH6tItlvfLBugVUEtgawwFU2sIe+cSdmRJJCTMZ5GEJyLxNyC/NIOAOGk67Fa8GNpOttO3Xz/1bHpXFD/g==
|
||||
|
||||
moment@2.29.2:
|
||||
version "2.29.2"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4"
|
||||
integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==
|
||||
moment@2.29.4:
|
||||
version "2.29.4"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
|
||||
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
|
||||
|
||||
mousetrap@1.6.5:
|
||||
version "1.6.5"
|
||||
|
||||
Reference in New Issue
Block a user