Merge pull request #426 from pkkid/conversion_actions

conversion_actions
This commit is contained in:
blacktwin 2020-04-12 22:34:51 -04:00 committed by GitHub
commit 0a77c74466
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 184 additions and 10 deletions

View file

@ -25,9 +25,9 @@ except ImportError:
from urllib import quote
try:
from urllib.parse import quote_plus
from urllib.parse import quote_plus, quote
except ImportError:
from urllib import quote_plus
from urllib import quote_plus, quote
try:
from urllib.parse import unquote

View file

@ -349,9 +349,30 @@ class TranscodeSession(PlexObject):
self.width = cast(int, data.attrib.get('width'))
@utils.registerPlexObject
class TranscodeJob(PlexObject):
""" Represents an Optimizing job.
TrancodeJobs are the process for optimizing conversions.
Active or paused optimization items. Usually one item as a time"""
TAG = 'TranscodeJob'
def _loadData(self, data):
self._data = data
self.generatorID = data.attrib.get('generatorID')
self.key = data.attrib.get('key')
self.progress = data.attrib.get('progress')
self.ratingKey = data.attrib.get('ratingKey')
self.size = data.attrib.get('size')
self.targetTagID = data.attrib.get('targetTagID')
self.thumb = data.attrib.get('thumb')
self.title = data.attrib.get('title')
self.type = data.attrib.get('type')
@utils.registerPlexObject
class Optimized(PlexObject):
""" Represents a Optimized item. """
""" Represents a Optimized item.
Optimized items are optimized and queued conversions items."""
TAG = 'Item'
def _loadData(self, data):
@ -363,10 +384,26 @@ class Optimized(PlexObject):
self.target = data.attrib.get('target')
self.targetTagID = data.attrib.get('targetTagID')
def remove(self):
""" Remove an Optimized item"""
key = '%s/%s' % (self._initpath, self.id)
self._server.query(key, method=self._server._session.delete)
def rename(self, title):
""" Rename an Optimized item"""
key = '%s/%s?Item[title]=%s' % (self._initpath, self.id, title)
self._server.query(key, method=self._server._session.put)
def reprocess(self, ratingKey):
""" Reprocess a removed Conversion item that is still a listed Optimize item"""
key = '%s/%s/%s/enable' % (self._initpath, self.id, ratingKey)
self._server.query(key, method=self._server._session.put)
@utils.registerPlexObject
class Conversion(PlexObject):
""" Represents a Conversion item. """
""" Represents a Conversion item.
Conversions are items queued for optimization or being actively optimized."""
TAG = 'Video'
def _loadData(self, data):
@ -403,6 +440,29 @@ class Conversion(PlexObject):
self.viewOffset = data.attrib.get('viewOffset')
self.year = data.attrib.get('year')
def remove(self):
""" Remove Conversion from queue """
key = '/playlists/%s/items/%s/%s/disable' % (self.playlistID, self.generatorID, self.ratingKey)
self._server.query(key, method=self._server._session.put)
def move(self, after):
""" Move Conversion items position in queue
after (int): Positional integer to move item
-1 Active conversion
OR
Use another conversion items playQueueItemID to move in front of
Example:
Move 5th conversion Item to active conversion
conversions[4].move('-1')
Move 4th conversion Item to 2nd in conversion queue
conversions[3].move(conversions[1].playQueueItemID)
"""
key = '%s/items/%s/move?after=%s' % (self._initpath, self.playQueueItemID, after)
self._server.query(key, method=self._server._session.put)
class MediaTag(PlexObject):
""" Base class for media tags used for filtering and searching your library

View file

@ -373,16 +373,35 @@ class PlexServer(PlexObject):
"""
return self.fetchItem('/playlists', title=title)
def optimizedItems(self):
def optimizedItems(self, removeAll=None):
""" Returns list of all :class:`~plexapi.media.Optimized` objects connected to server. """
if removeAll is True:
key = '/playlists/generators?type=42'
self.query(key, method=self._server._session.delete)
else:
backgroundProcessing = self.fetchItem('/playlists?type=42')
return self.fetchItems('%s/items' % backgroundProcessing.key, cls=Optimized)
def optimizedItem(self, optimizedID):
""" Returns single queued optimized item :class:`~plexapi.media.Video` object.
Allows for using optimized item ID to connect back to source item.
"""
backgroundProcessing = self.fetchItem('/playlists?type=42')
return self.fetchItems('%s/items' % backgroundProcessing.key, cls=Optimized)
return self.fetchItem('%s/items/%s/items' % (backgroundProcessing.key, optimizedID))
def conversions(self):
def conversions(self, pause=None):
""" Returns list of all :class:`~plexapi.media.Conversion` objects connected to server. """
if pause is True:
self.query('/:/prefs?BackgroundQueueIdlePaused=1', method=self._server._session.put)
elif pause is False:
self.query('/:/prefs?BackgroundQueueIdlePaused=0', method=self._server._session.put)
else:
return self.fetchItems('/playQueues/1', cls=Conversion)
return self.fetchItems('/playQueues/1', cls=Conversion)
def currentBackgroundProcess(self):
""" Returns list of all :class:`~plexapi.media.TranscodeJob` objects running or paused on server. """
return self.fetchItems('/status/sessions/background')
def query(self, key, method=None, headers=None, timeout=None, **kwargs):
""" Main method used to handle HTTPS requests to the Plex server. This method helps

View file

@ -2,7 +2,7 @@
from plexapi import media, utils
from plexapi.exceptions import BadRequest, NotFound
from plexapi.base import Playable, PlexPartialObject
from plexapi.compat import quote_plus
from plexapi.compat import quote_plus, urlencode
import os
@ -126,6 +126,82 @@ class Video(PlexPartialObject):
return self.fetchItems('%s/posters' % self.key, cls=media.Poster)
def optimize(self, title=None, target="", targetTagID=None, locationID=-1, policyScope='all',
policyValue="", policyUnwatched=0, videoQuality=None, deviceProfile=None):
""" Optimize item
locationID (int): -1 in folder with orginal items
2 library path
target (str): custom quality name.
if none provided use "Custom: {deviceProfile}"
targetTagID (int): Default quality settings
1 Mobile
2 TV
3 Original Quality
deviceProfile (str): Android, IOS, Universal TV, Universal Mobile, Windows Phone,
Windows, Xbox One
Example:
Optimize for Mobile
item.optimize(targetTagID="Mobile") or item.optimize(targetTagID=1")
Optimize for Android 10 MBPS 1080p
item.optimize(deviceProfile="Android", videoQuality=10)
Optimize for IOS Original Quality
item.optimize(deviceProfile="IOS", videoQuality=-1)
* see sync.py VIDEO_QUALITIES for additional information for using videoQuality
"""
tagValues = [1, 2, 3]
tagKeys = ["Mobile", "TV", "Original Quality"]
tagIDs = tagKeys + tagValues
if targetTagID not in tagIDs and (deviceProfile is None or videoQuality is None):
raise BadRequest('Unexpected or missing quality profile.')
if isinstance(targetTagID, str):
tagIndex = tagKeys.index(targetTagID)
targetTagID = tagValues[tagIndex]
if title is None:
title = self.title
backgroundProcessing = self.fetchItem('/playlists?type=42')
key = '%s/items?' % backgroundProcessing.key
params = {
'Item[type]': 42,
'Item[target]': target,
'Item[targetTagID]': targetTagID if targetTagID else '',
'Item[locationID]': locationID,
'Item[Policy][scope]': policyScope,
'Item[Policy][value]': policyValue,
'Item[Policy][unwatched]': policyUnwatched
}
if deviceProfile:
params['Item[Device][profile]'] = deviceProfile
if videoQuality:
from plexapi.sync import MediaSettings
mediaSettings = MediaSettings.createVideo(videoQuality)
params['Item[MediaSettings][videoQuality]'] = mediaSettings.videoQuality
params['Item[MediaSettings][videoResolution]'] = mediaSettings.videoResolution
params['Item[MediaSettings][maxVideoBitrate]'] = mediaSettings.maxVideoBitrate
params['Item[MediaSettings][audioBoost]'] = ''
params['Item[MediaSettings][subtitleSize]'] = ''
params['Item[MediaSettings][musicBitrate]'] = ''
params['Item[MediaSettings][photoQuality]'] = ''
titleParam = {'Item[title]': title}
section = self._server.library.sectionByID(self.librarySectionID)
params['Item[Location][uri]'] = 'library://' + section.uuid + '/item/' + \
quote_plus(self.key + '?includeExternalMedia=1')
data = key + urlencode(params) + '&' + urlencode(titleParam)
return self._server.query(data, method=self._server._session.put)
def sync(self, videoQuality, client=None, clientId=None, limit=None, unwatched=False, title=None):
""" Add current video (movie, tv-show, season or episode) as sync item for specified device.
See :func:`plexapi.myplex.MyPlexAccount.sync()` for possible exceptions.

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import pytest
from datetime import datetime
from time import sleep
from plexapi.exceptions import BadRequest, NotFound
from . import conftest as utils
@ -685,4 +686,22 @@ def test_video_exists_accessible(movie, episode):
episode.reload()
assert episode.media[0].parts[0].exists is True
assert episode.media[0].parts[0].accessible is True
@pytest.mark.skip(reason='broken? assert len(plex.conversions()) == 1 may fail on some builds')
def test_video_optimize(movie, plex):
plex.optimizedItems(removeAll=True)
movie.optimize(targetTagID=1)
plex.conversions(pause=True)
sleep(1)
assert len(plex.optimizedItems()) == 1
assert len(plex.conversions()) == 1
conversion = plex.conversions()[0]
conversion.remove()
assert len(plex.conversions()) == 0
assert len(plex.optimizedItems()) == 1
optimized = plex.optimizedItems()[0]
video = plex.optimizedItem(optimizedID=optimized.id)
assert movie.key == video.key
plex.optimizedItems(removeAll=True)
assert len(plex.optimizedItems()) == 0