Skip to content

Commit

Permalink
Merge branch 'release/0.10.4'
Browse files Browse the repository at this point in the history
  • Loading branch information
Anders Jensen committed Sep 21, 2018
2 parents 0c88dfd + 4b1d379 commit 8ab94d2
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 11 deletions.
56 changes: 55 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,64 @@ The _allow remote_ option is to allow remote add and stream of torrents.
* [ ] Better feedback in interface about streams
* [ ] Better feedback when using API
* [x] Reverse proxy improvement (e.g. port different than bind port)
* [ ] Fix problems when removing torrent from Deluge (sea of errors)

# HTTP API Usage

## Prerequisite

Install and enable the plugin. Afterwards, head into Streaming settings and enable "Allow remote control".
The URL found in the "Remote control url" field is where the API can be reached. The auth used is Basic Auth.

## Usage

There is only one end-point and that is where a torrent stream can be requested.

Both return the same responses and all responses are JSON encoded.
All successfully authenticated responses have status code 200.

## POST /streaming/stream

POST body must be the raw torrent you want to stream. No form formatting or anything can be used.

List of URL GET Arguments

* **path**: Path inside the torrent file to either a folder or a file you want to stream. The plugin will try to guess the best one. **Optional**. **Default**: '' (i.e. find the best file in the whole torrent)
* **infohash**: Infohash of the torrent you want to stream, can make it a bit faster as it can avoid reading POST body. **Optional**.
* **label**: If label plugin is enabled and the torrent is actually added then give the torrent this label. **Optional**. **Default**: ''
* **wait_for_end_pieces**: Wait for the first and last piece in the streamed file to be fully downloaded. Can be necessary for some video players. It also enforces that the torrent can be actually downloaded. If the key exist with any (even empty) value, the feature is enabled. **Optional**. **Default**: false

## GET /streaming/stream

* **infohash**: Does the same as when POSTed. **Mandatory**.
* **path**: Does the same as when POSTed. **Optional**.
* **wait_for_end_pieces**: Does the same as when POSTed. **Optional**.

## Success Response

```json
{
"status": "success", # Always equals this
"filename" "horse.mkv", # Filename of the streamed torrent
"url": "http://example.com/" # URL where the file can be reached by e.g. a media player
}
```

## Error Response

```json
{
"status": "error", # Always equals this
"message" "Torrent failed" # description for why it failed
}
```

# Version Info

## Version Unreleased
## Version 0.10.4
* Trying to set max priority less as it destroys performance

## Version 0.10.3
* Added label support
* Reverse proxy config / replace URL config
* Ensure internal Deluge state is updated before trying to use it
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
__plugin_name__ = "Streaming"
__author__ = "Anders Jensen"
__author_email__ = "johndoee@tidalstream.org"
__version__ = "0.10.3"
__version__ = "0.10.4"
__url__ = "https://github.com/JohnDoee/deluge-streaming"
__license__ = "GPLv3"
__description__ = "Enables streaming of files while downloading them."
Expand Down
54 changes: 45 additions & 9 deletions streaming/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@
STREAMABLE_EXTENSIONS = set(VIDEO_STREAMABLE_EXTENSIONS + AUDIO_STREAMABLE_EXTENSIONS)
TORRENT_CLEANUP_INTERVAL = timedelta(minutes=30)
MAX_FILE_PRIORITY = 2
MAX_PIECE_PRIORITY = 7
MIN_WAIT_PIECE_PRIORITY_DELAY = timedelta(seconds=5)
WITHIN_CHAIN_PERCENTAGE = 0.10
MIN_PIECE_COUNT_FOR_CHAIN_CONSIDERATION = 40
MIN_CHAIN_WAIT_DELAY = timedelta(seconds=8)


DEFAULT_PREFS = {
'ip': '127.0.0.1',
Expand Down Expand Up @@ -128,6 +134,7 @@ def __init__(self, torrent_handler, infohash):
self.readers = {}
self.cycle_lock = defer.DeferredLock()
self.last_activity = datetime.now()
self.waited_pieces = set()

self.torrent = get_torrent(infohash)
status = self.torrent.get_status(['piece_length'])
Expand Down Expand Up @@ -161,23 +168,49 @@ def can_read(self, from_byte):
last_available_piece = piece

if last_available_piece is None:
logger.debug('Since we are waiting for a piece, setting priority for %s to max' % (needed_piece, ))
self.torrent.handle.set_piece_deadline(needed_piece, 0)
self.torrent.handle.piece_priority(needed_piece, 7)
logger.debug('Since we are waiting for a piece, we need to check if we should set piece %s to max' % (needed_piece, ))

is_next_in_chain = False
f = self.get_file_from_offset(from_byte)
file_piece_count = (f['size'] // self.piece_length) + 1

if file_piece_count <= MIN_PIECE_COUNT_FOR_CHAIN_CONSIDERATION:
is_next_in_chain = True
else:
best_reader_from_byte = max(reader[1] for reader in self.readers.values() if reader[1] <= from_byte)
best_reader_piece = best_reader_from_byte // self.piece_length
downloading_pieces = self.get_currently_downloading()
for unfinished_piece, status in enumerate(self.torrent.status.pieces[best_reader_piece:], best_reader_piece):
if not status and unfinished_piece not in downloading_pieces:
break

piece_diff = best_reader_piece - unfinished_piece - 1
if unfinished_piece >= best_reader_piece or piece_diff / file_piece_count <= WITHIN_CHAIN_PERCENTAGE:
is_next_in_chain = True

if not is_next_in_chain:
logger.debug('Not a next-in-chain piece, setting priority now')
self.torrent.handle.set_piece_deadline(needed_piece, 0)
self.torrent.handle.piece_priority(needed_piece, MAX_PIECE_PRIORITY)

file_priorities = self.torrent.get_file_priorities()
if file_priorities[f['index']] != MAX_FILE_PRIORITY:
logger.debug('Also setting file to max %r' % (f, ))
file_priorities[f['index']] = MAX_FILE_PRIORITY
self.torrent.set_file_priorities(file_priorities)

for _ in range(300):
for i in range(300):
if self.torrent.status.pieces[needed_piece]:
break

if not reactor.running:
return

if is_next_in_chain and i == MIN_CHAIN_WAIT_DELAY.total_seconds() * 5 and needed_piece not in self.get_currently_downloading():
logger.debug('Next in chain waiting failed, setting priority')
self.torrent.handle.set_piece_deadline(needed_piece, 0)
self.torrent.handle.piece_priority(needed_piece, MAX_PIECE_PRIORITY)

time.sleep(0.2)
status = self.torrent.get_status(['pieces'])

Expand Down Expand Up @@ -263,6 +296,10 @@ def _cycle(self):
else:
file_ranges[path] = from_byte

reader_piece = from_byte // self.piece_length
self.torrent.handle.set_piece_deadline(reader_piece, 0)
self.torrent.handle.piece_priority(reader_piece, MAX_PIECE_PRIORITY)

for fileset_hash, fileset in self.filesets.items():
if path in fileset['files']:
if fileset_hash in fileset_ranges:
Expand Down Expand Up @@ -313,8 +350,6 @@ def _cycle(self):

if piece < current_piece:
self.torrent.handle.piece_priority(piece, 0)
elif piece == current_piece:
self.torrent.handle.piece_priority(piece, 7)
else:
self.torrent.handle.piece_priority(piece, 1)

Expand Down Expand Up @@ -523,9 +558,10 @@ def stream(self, infohash, path, wait_for_end_pieces=False):
wait_for_pieces.append(piece - 1)

logger.debug('We want first and last piece first, these are the pieces: %r' % (wait_for_pieces, ))
for piece in wait_for_pieces:
torrent.handle.set_piece_deadline(piece, 0)
torrent.handle.piece_priority(piece, 7)
if wait_for_pieces:
for piece in wait_for_pieces:
torrent.handle.set_piece_deadline(piece, 0)
torrent.handle.piece_priority(piece, MAX_PIECE_PRIORITY)

for _ in range(220):
status = torrent.get_status(['pieces'])
Expand Down

0 comments on commit 8ab94d2

Please sign in to comment.