mirror of
https://github.com/JohnDoee/deluge-streaming/
synced 2026-07-01 07:31:17 -07:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e92f9029a4 | ||
|
|
a483bfe599 | ||
|
|
f3ee7f4270 | ||
|
|
91e8da6dfd | ||
|
|
2e4b166528 | ||
|
|
aaf70bbfc8 | ||
|
|
52661abf52 | ||
|
|
a9dae5bc90 |
14
README.md
14
README.md
@@ -11,6 +11,11 @@ the user to stream a file using HTTP.
|
||||
Technically, it tries to download the part of a file the user requests and
|
||||
downloads ahead, this enables seeking in video files.
|
||||
|
||||
## Where to download
|
||||
|
||||
You can download this release on Github. Look for the "releases" tab on the repository page.
|
||||
Under that tab, eggs for Python 2.6 and 2.7 should exist.
|
||||
|
||||
## How to use
|
||||
|
||||
* Install plugin
|
||||
@@ -32,12 +37,17 @@ The _allow remote_ option is to allow remote add and stream of torrents.
|
||||
|
||||
## ToDo
|
||||
|
||||
* Add support for the WebUI
|
||||
* There are a few situations where an uncaught exception is thrown.
|
||||
* Add feedback when preparing stream.
|
||||
|
||||
# Version Info
|
||||
|
||||
## Version 0.4.1
|
||||
* Fixed bug with old Deluge versions
|
||||
|
||||
## Version 0.4.0
|
||||
* Added WebUI support
|
||||
* Improved scheduling algorithm
|
||||
|
||||
## Version 0.3
|
||||
* Fixed bug when streaming multiple files.
|
||||
* Changed to try to prioritize end pieces more aggressively to not leave them hanging.
|
||||
|
||||
2
setup.py
2
setup.py
@@ -42,7 +42,7 @@ from setuptools import setup
|
||||
__plugin_name__ = "Streaming"
|
||||
__author__ = "John Doee"
|
||||
__author_email__ = "johndoee@tidalstream.org"
|
||||
__version__ = "0.3.1"
|
||||
__version__ = "0.4.1"
|
||||
__url__ = "https://github.com/JohnDoee/deluge-streaming"
|
||||
__license__ = "GPLv3"
|
||||
__description__ = "Enables streaming of files while downloading them."
|
||||
|
||||
@@ -42,6 +42,7 @@ import json
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import time
|
||||
import urllib
|
||||
|
||||
from collections import defaultdict
|
||||
@@ -324,11 +325,16 @@ class TorrentHandler(object):
|
||||
current_buffer_offset = 20
|
||||
break
|
||||
|
||||
currently_downloading = set()
|
||||
for peer in self.torrent.handle.get_peer_info():
|
||||
if peer.downloading_piece_index != -1:
|
||||
currently_downloading.add(peer.downloading_piece_index)
|
||||
|
||||
status_increase_count = 0
|
||||
current_buffer = 0
|
||||
found_buffer_end = False
|
||||
for chunk, chunk_status in enumerate(self.torrent.status.pieces[offset:tf.last_chunk+1], offset):
|
||||
if not chunk_status:
|
||||
if not chunk_status and chunk not in currently_downloading:
|
||||
if not found_buffer_end:
|
||||
logger.debug('Found buffer end at %s, have a buffer of %s' % (chunk, current_buffer))
|
||||
found_buffer_end = True
|
||||
@@ -339,7 +345,7 @@ class TorrentHandler(object):
|
||||
self.torrent_handle.piece_priority(chunk, 1)
|
||||
|
||||
status_increase_count += 1
|
||||
elif not found_buffer_end:
|
||||
elif not found_buffer_end and chunk not in currently_downloading:
|
||||
current_buffer += 1
|
||||
|
||||
if status_increase_count >= max(MIN_QUEUE_CHUNKS, current_buffer-current_buffer_offset):
|
||||
@@ -350,6 +356,7 @@ class TorrentHandler(object):
|
||||
return bool(handled_heads)
|
||||
|
||||
def update_chunk_priorities(self): # TODO: check if torrent still exists
|
||||
start_time = time.time()
|
||||
file_progress = self.torrent.get_status(['file_progress'])['file_progress']
|
||||
incomplete_files = False
|
||||
|
||||
@@ -380,21 +387,22 @@ class TorrentHandler(object):
|
||||
del self.core.torrent_handlers[self.torrent_id]
|
||||
return
|
||||
|
||||
reactor.callLater(2, self.update_chunk_priorities)
|
||||
logger.debug('Took %s seconds to handle a loop' % (time.time() - start_time))
|
||||
reactor.callLater(1, self.update_chunk_priorities)
|
||||
|
||||
def blackhole_all_pieces(self, first_chunk, last_chunk):
|
||||
for chunk in range(first_chunk, last_chunk+1):
|
||||
self.torrent_handle.piece_priority(chunk, 0)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def get_file(self, filepath):
|
||||
def get_file(self, filepath_or_index):
|
||||
status = self.torrent.get_status(['piece_length', 'files', 'file_priorities', 'file_progress', 'state', 'save_path'])
|
||||
pieces = self.torrent.status.pieces
|
||||
piece_length = status['piece_length']
|
||||
files = status['files']
|
||||
|
||||
for f, priority, progress in zip(files, status['file_priorities'], status['file_progress']):
|
||||
if f['path'] == filepath:
|
||||
for i, f, priority, progress in zip(range(len(files)), files, status['file_priorities'], status['file_progress']):
|
||||
if i == filepath_or_index or f['path'] == filepath_or_index:
|
||||
f['first_piece'] = f['offset'] / piece_length
|
||||
f['last_piece'] = (f['offset'] + f['size']) / piece_length
|
||||
f['pieces'] = pieces[f['first_piece']:f['last_piece']+1]
|
||||
@@ -471,10 +479,13 @@ class Core(CorePluginBase):
|
||||
|
||||
self.site = server.Site(self.resource)
|
||||
|
||||
session = component.get("Core").session
|
||||
settings = session.get_settings()
|
||||
settings['prioritize_partial_pieces'] = True
|
||||
session.set_settings(settings)
|
||||
try:
|
||||
session = component.get("Core").session
|
||||
settings = session.get_settings()
|
||||
settings['prioritize_partial_pieces'] = True
|
||||
session.set_settings(settings)
|
||||
except AttributeError:
|
||||
logger.warning('Unable to exclude partial pieces')
|
||||
|
||||
self.torrent_handlers = {}
|
||||
|
||||
@@ -534,14 +545,14 @@ class Core(CorePluginBase):
|
||||
|
||||
@export
|
||||
@defer.inlineCallbacks
|
||||
def stream_torrent(self, tid, filepath):
|
||||
def stream_torrent(self, tid, filepath_or_index):
|
||||
try:
|
||||
torrent_handler = yield self.get_torrent_handler(tid)
|
||||
except UnknownTorrentException: # torrent isn't added yet
|
||||
defer.returnValue({'status': 'error', 'message': 'torrent_not_found'})
|
||||
|
||||
try:
|
||||
tf = yield torrent_handler.get_file(filepath)
|
||||
tf = yield torrent_handler.get_file(filepath_or_index)
|
||||
except UnknownFileException:
|
||||
defer.returnValue({'status': 'error', 'message': 'file_not_found'})
|
||||
|
||||
|
||||
@@ -31,20 +31,139 @@ Copyright:
|
||||
statement from all source files in the program, then also delete it here.
|
||||
*/
|
||||
|
||||
PreferencePage = Ext.extend(Ext.Panel, {
|
||||
title: 'Streaming',
|
||||
border: false,
|
||||
layout: 'form',
|
||||
|
||||
initComponent: function() {
|
||||
PreferencePage.superclass.initComponent.call(this);
|
||||
|
||||
var om = this.optionsManager = new Deluge.OptionsManager();
|
||||
this.on('show', this.onPageShow, this);
|
||||
|
||||
var fieldset = this.add({
|
||||
xtype: 'fieldset',
|
||||
border: false,
|
||||
title: 'Streaming',
|
||||
style: 'margin-bottom: 0px; padding-bottom: 0px; padding-top: 5px',
|
||||
autoHeight: true,
|
||||
labelWidth: 110,
|
||||
defaultType: 'textfield',
|
||||
defaults: {
|
||||
width: 180,
|
||||
}
|
||||
});
|
||||
|
||||
om.bind('port', fieldset.add({
|
||||
name: 'port',
|
||||
fieldLabel: _('Port'),
|
||||
decimalPrecision: 0,
|
||||
minValue: -1,
|
||||
maxValue: 99999
|
||||
}));
|
||||
|
||||
om.bind('ip', fieldset.add({
|
||||
name: 'ip',
|
||||
fieldLabel: 'IP'
|
||||
}));
|
||||
|
||||
om.bind('reset_complete', fieldset.add({
|
||||
xtype: 'checkbox',
|
||||
name: 'reset_complete',
|
||||
fieldLabel: 'Reset "do not download" when streamed file is complete',
|
||||
}));
|
||||
|
||||
om.bind('allow_remote', fieldset.add({
|
||||
xtype: 'checkbox',
|
||||
name: 'allow_remote',
|
||||
fieldLabel: 'Allow remote control checkbox',
|
||||
}));
|
||||
|
||||
om.bind('remote_username', fieldset.add({
|
||||
name: 'remote_username',
|
||||
fieldLabel: 'Remote username'
|
||||
}));
|
||||
|
||||
om.bind('remote_password', fieldset.add({
|
||||
name: 'remote_password',
|
||||
inputType: 'password',
|
||||
fieldLabel: 'Remote password'
|
||||
}));
|
||||
},
|
||||
|
||||
onApply: function() {
|
||||
var changed = this.optionsManager.getDirty();
|
||||
if (!Ext.isObjectEmpty(changed)) {
|
||||
deluge.client.streaming.set_config(changed, {
|
||||
success: this.onSetConfig,
|
||||
scope: this
|
||||
});
|
||||
|
||||
for (var key in deluge.config) {
|
||||
deluge.config[key] = this.optionsManager.get(key);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onSetConfig: function() {
|
||||
this.optionsManager.commit();
|
||||
},
|
||||
|
||||
onGotConfig: function(config) {
|
||||
this.optionsManager.set(config);
|
||||
},
|
||||
|
||||
onPageShow: function() {
|
||||
deluge.client.streaming.get_config({
|
||||
success: this.onGotConfig,
|
||||
scope: this
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
StreamingPlugin = Ext.extend(Deluge.Plugin, {
|
||||
constructor: function(config) {
|
||||
config = Ext.apply({
|
||||
name: "Streaming"
|
||||
}, config);
|
||||
StreamingPlugin.superclass.constructor.call(this, config);
|
||||
},
|
||||
'name': 'Streaming',
|
||||
|
||||
onDisable: function() {
|
||||
|
||||
deluge.menus.filePriorities.remove('streamthis');
|
||||
|
||||
deluge.preferences.selectPage(_('Plugins'));
|
||||
deluge.preferences.removePage(this.prefsPage);
|
||||
this.prefsPage.destroy();
|
||||
},
|
||||
|
||||
onEnable: function() {
|
||||
|
||||
onEnable: function() {
|
||||
this.prefsPage = new PreferencePage();
|
||||
deluge.preferences.addPage(this.prefsPage);
|
||||
|
||||
console.log('Streaming plugin loaded');
|
||||
deluge.menus.filePriorities.addMenuItem({
|
||||
id: 'streamthis',
|
||||
text: 'Stream this file',
|
||||
iconCls: 'icon-down',
|
||||
handler: function (item, event) {
|
||||
deluge.menus.filePriorities.hide();
|
||||
var files = deluge.details.items.items[2];
|
||||
var nodes = files.getSelectionModel().getSelectedNodes();
|
||||
if (nodes) {
|
||||
var fileIndex = nodes[0].attributes.fileIndex;
|
||||
var tid = files.torrentId;
|
||||
if (fileIndex >= 0) {
|
||||
deluge.client.streaming.stream_torrent(tid, fileIndex, {
|
||||
success: function (result) {
|
||||
if (result.status == 'success') {
|
||||
Ext.Msg.alert('Stream ready', 'URL for stream: <a target="_blank" href="' + result.url + '">' + result.url + '</a>');
|
||||
} else {
|
||||
Ext.Msg.alert('Stream failed', 'Error message: ' + result.message);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
new StreamingPlugin();
|
||||
Deluge.registerPlugin('Streaming', StreamingPlugin);
|
||||
@@ -46,10 +46,4 @@ from common import get_resource
|
||||
|
||||
class WebUI(WebPluginBase):
|
||||
|
||||
scripts = [get_resource("streaming.js")]
|
||||
|
||||
def enable(self):
|
||||
pass
|
||||
|
||||
def disable(self):
|
||||
pass
|
||||
scripts = [get_resource("streaming.js")]
|
||||
Reference in New Issue
Block a user