mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-22 03:33:08 +00:00
Merge pull request #426 from pkkid/conversion_actions
conversion_actions
This commit is contained in:
commit
0a77c74466
5 changed files with 184 additions and 10 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
Loading…
Reference in a new issue