Commit 8c974796 authored by Cédric Bellegarde's avatar Cédric Bellegarde

Restore streaming support. Use StorageType.EXTERNAL. Fix #2236

parent ce5dcb4e
Pipeline #197223 passed with stage
in 3 minutes and 27 seconds
......@@ -16,7 +16,6 @@ gi.require_version("GstAudio", "1.0")
gi.require_version("GstPbutils", "1.0")
gi.require_version("TotemPlParser", "1.0")
from gi.repository import Gtk, Gio, GLib, Gdk, Gst, GstPbutils
from gi.repository.Gio import FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE
Gst.init(None)
GstPbutils.pb_utils_init()
......@@ -24,12 +23,11 @@ from threading import current_thread
from pickle import dump
from signal import signal, SIGINT, SIGTERM
from urllib.parse import urlparse
from gettext import gettext as _
from lollypop.utils import init_proxy_from_gnome, emit_signal
from lollypop.application_actions import ApplicationActions
from lollypop.utils_file import is_audio, is_pls, install_youtube_dl
from lollypop.define import LOLLYPOP_DATA_PATH, ScanType, StorageType
from lollypop.utils_file import get_file_type, install_youtube_dl
from lollypop.define import LOLLYPOP_DATA_PATH, ScanType, StorageType, FileType
from lollypop.database import Database
from lollypop.player import Player
from lollypop.inhibitor import Inhibitor
......@@ -479,22 +477,23 @@ class Application(Gtk.Application, ApplicationActions):
audio_uris = []
playlist_uris = []
for uri in args[1:]:
try:
uri = GLib.filename_to_uri(uri)
except:
pass
f = Gio.File.new_for_uri(uri)
if not f.query_exists():
uri = GLib.filename_to_uri(
"%s/%s" % (GLib.get_current_dir(), uri))
parsed = urlparse(uri)
if parsed.scheme not in ["http", "https"]:
try:
uri = GLib.filename_to_uri(uri)
except:
pass
f = Gio.File.new_for_uri(uri)
info = f.query_info(FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
Gio.FileQueryInfoFlags.NONE)
if is_audio(info):
audio_uris.append(uri)
elif is_pls(info):
# Try ./filename
if not f.query_exists():
uri = GLib.filename_to_uri(
"%s/%s" % (GLib.get_current_dir(), uri))
print(uri)
f = Gio.File.new_for_uri(uri)
file_type = get_file_type(uri)
if file_type == FileType.PLS:
playlist_uris.append(uri)
elif info.get_file_type() == Gio.FileType.DIRECTORY:
else:
audio_uris.append(uri)
if playlist_uris:
self.__parse_uris(playlist_uris, audio_uris)
......@@ -520,16 +519,7 @@ class Application(Gtk.Application, ApplicationActions):
if playlist_uris:
self.__parse_uris(playlist_uris, audio_uris)
else:
to_scan_uris = []
for uri in audio_uris:
parsed = urlparse(uri)
if parsed.scheme in ["http", "https"]:
self.notify.send(
"Lollypop",
_("Lollypop does not support http streams"))
else:
to_scan_uris.append(uri)
self.scanner.update(ScanType.EXTERNAL, to_scan_uris)
self.scanner.update(ScanType.EXTERNAL, audio_uris)
def __on_entry_parsed(self, parser, uri, metadata, audio_uris):
"""
......
......@@ -65,7 +65,7 @@ class AlbumArt:
return cache_path_jpg
except Exception as e:
Logger.error("Art::get_album_cache_path(): %s" % e)
return None
return None
def get_album_artwork_uri(self, album):
"""
......@@ -196,50 +196,51 @@ class AlbumArt:
if optimized_blur:
pixbuf = self.load_behaviour(pixbuf, None,
width, height, behaviour)
return pixbuf
else:
# Use favorite folder artwork
if pixbuf is None:
uri = self.get_album_artwork_uri(album)
data = None
if uri is not None:
f = Gio.File.new_for_uri(uri)
(status, data, tag) = f.load_contents(None)
bytes = GLib.Bytes.new(data)
stream = Gio.MemoryInputStream.new_from_bytes(bytes)
pixbuf = GdkPixbuf.Pixbuf.new_from_stream(
stream, None)
stream.close()
# Use tags artwork
if pixbuf is None and album.tracks and\
album.storage_type & (StorageType.COLLECTION |
StorageType.EXTERNAL):
try:
track = choice(album.tracks)
pixbuf = self.pixbuf_from_tags(track.uri)
except Exception as e:
Logger.error("AlbumArt::get_album_artwork(): %s", e)
# Use folder artwork
if pixbuf is None and\
album.storage_type & (StorageType.COLLECTION |
StorageType.EXTERNAL):
uri = self.get_first_album_artwork(album)
# Look in album folder
if uri is not None:
f = Gio.File.new_for_uri(uri)
(status, data, tag) = f.load_contents(None)
bytes = GLib.Bytes.new(data)
stream = Gio.MemoryInputStream.new_from_bytes(bytes)
pixbuf = GdkPixbuf.Pixbuf.new_from_stream(
stream, None)
stream.close()
if pixbuf is None:
self.cache_album_artwork(album.id)
return None
pixbuf = self.load_behaviour(pixbuf, cache_path_jpg,
width, height, behaviour)
return pixbuf
# Use favorite folder artwork
if pixbuf is None:
uri = self.get_album_artwork_uri(album)
print(uri)
data = None
if uri is not None:
f = Gio.File.new_for_uri(uri)
(status, data, tag) = f.load_contents(None)
bytes = GLib.Bytes.new(data)
stream = Gio.MemoryInputStream.new_from_bytes(bytes)
pixbuf = GdkPixbuf.Pixbuf.new_from_stream(
stream, None)
stream.close()
# Use tags artwork
if pixbuf is None and album.tracks and\
album.storage_type & (StorageType.COLLECTION |
StorageType.EXTERNAL):
try:
track = choice(album.tracks)
pixbuf = self.pixbuf_from_tags(track.uri)
except Exception as e:
Logger.error("AlbumArt::get_album_artwork(): %s", e)
# Use folder artwork
if pixbuf is None and\
album.storage_type & (StorageType.COLLECTION |
StorageType.EXTERNAL):
uri = self.get_first_album_artwork(album)
# Look in album folder
if uri is not None:
f = Gio.File.new_for_uri(uri)
(status, data, tag) = f.load_contents(None)
bytes = GLib.Bytes.new(data)
stream = Gio.MemoryInputStream.new_from_bytes(bytes)
pixbuf = GdkPixbuf.Pixbuf.new_from_stream(
stream, None)
stream.close()
if pixbuf is None:
self.cache_album_artwork(album.id)
return None
pixbuf = self.load_behaviour(pixbuf, cache_path_jpg,
width, height, behaviour)
return pixbuf
except Exception as e:
Logger.error("AlbumArt::get_album_artwork(): %s -> %s" % (uri, e))
return None
......@@ -362,8 +363,7 @@ class AlbumArt:
Check if album uri exists, update if not
@param album as Album
"""
if not album.storage_type & (StorageType.COLLECTION |
StorageType.EXTERNAL):
if not album.storage_type & StorageType.COLLECTION:
return
d = Gio.File.new_for_uri(album.uri)
if not d.query_exists():
......
......@@ -23,6 +23,7 @@ from gi.repository.Gio import FILE_ATTRIBUTE_STANDARD_NAME, \
from gettext import gettext as _
from time import time
from urllib.parse import urlparse
from multiprocessing import cpu_count
from lollypop.collection_item import CollectionItem
......@@ -340,17 +341,18 @@ class CollectionScanner(GObject.GObject, TagReader):
GLib.idle_add(App().window.container.progress.set_fraction,
new_fraction, self)
def __finish(self, new_track_ids):
def __finish(self, items):
"""
Notify from main thread when scan finished
@param new_track_ids as int
@param items as [CollectionItem]
"""
track_ids = [item.track_id for item in items]
self.__thread = None
Logger.info("Scan finished")
App().lookup_action("update_db").set_enabled(True)
App().window.container.progress.set_fraction(1.0, self)
self.stop()
emit_signal(self, "scan-finished", new_track_ids)
emit_signal(self, "scan-finished", track_ids)
# Update max count value
App().albums.update_max_count()
# Update featuring
......@@ -379,18 +381,24 @@ class CollectionScanner(GObject.GObject, TagReader):
Get all tracks and dirs in uris
@param scan_type as ScanType
@param uris as string
@return (tracks [mtimes: int, uri: str], dirs as [uri: str])
@return ([(int, str)], [str], [str])
([(mtime, file)], [dir], [stream])
"""
files = []
dirs = []
streams = []
walk_uris = []
# Check collection exists
for uri in uris:
f = Gio.File.new_for_uri(uri)
if f.query_exists():
walk_uris.append(uri)
parsed = urlparse(uri)
if parsed.scheme in ["http", "https"]:
streams.append(uri)
else:
return (None, None)
f = Gio.File.new_for_uri(uri)
if f.query_exists():
walk_uris.append(uri)
else:
return (None, None, None)
while walk_uris:
uri = walk_uris.pop(0)
......@@ -424,7 +432,7 @@ class CollectionScanner(GObject.GObject, TagReader):
Logger.error("CollectionScanner::__get_objects_for_uris(): %s"
% e)
files.sort(reverse=True)
return (files, dirs)
return (files, dirs, streams)
@profile
def __scan(self, scan_type, uris):
......@@ -437,7 +445,8 @@ class CollectionScanner(GObject.GObject, TagReader):
try:
SqlCursor.add(App().db)
App().art.clean_rounded()
(files, dirs) = self.__get_objects_for_uris(scan_type, uris)
(files, dirs, streams) = self.__get_objects_for_uris(
scan_type, uris)
if files is None:
App().notify.send("Lollypop",
_("Scan disabled, missing collection"))
......@@ -450,7 +459,7 @@ class CollectionScanner(GObject.GObject, TagReader):
# Get mtime of all tracks to detect which has to be updated
db_mtimes = App().tracks.get_mtimes()
# * 2 => Scan + Save
self.__progress_total = len(files) * 2
self.__progress_total = len(files) * 2 + len(streams)
self.__progress_count = 0
self.__progress_fraction = 0
# Min: 1 thread, Max: 5 threads
......@@ -470,31 +479,30 @@ class CollectionScanner(GObject.GObject, TagReader):
else:
storage_type = StorageType.COLLECTION
# Start getting files and populating DB
track_ids = []
items = []
i = 0
while threads:
thread = threads[i]
if not thread.isAlive():
threads.remove(thread)
track_ids += self.__save_in_db(storage_type)
items += self.__save_in_db(storage_type)
if i >= len(threads) - 1:
i = 0
else:
i += 1
self.__remove_old_tracks(db_uris, scan_type)
if scan_type != ScanType.EXTERNAL:
self.__add_monitor(dirs)
GLib.idle_add(self.__finish, track_ids)
# Add streams to DB, only happening on command line/m3u files
items += self.__save_streams_in_db(streams, storage_type)
self.__remove_old_tracks(db_uris, scan_type)
if scan_type == ScanType.EXTERNAL:
track_ids = []
for (mtime, uri) in files:
track_id = App().tracks.get_id_by_uri(uri)
track_ids.append(track_id)
albums = tracks_to_albums(
[Track(track_id) for track_id in track_ids])
[Track(item.track_id) for item in items])
App().player.play_albums(albums)
else:
self.__add_monitor(dirs)
GLib.idle_add(self.__finish, items)
self.__tags = {}
self.__pending_new_artist_ids = []
except Exception as e:
......@@ -575,9 +583,10 @@ class CollectionScanner(GObject.GObject, TagReader):
"""
Save current tags into DB
@param storage_type as StorageType
@return [CollectionItem]
"""
items = []
track_ids = []
notify_index = 0
previous_album_id = None
for uri in list(self.__tags.keys()):
# Handle a stop request
......@@ -587,21 +596,39 @@ class CollectionScanner(GObject.GObject, TagReader):
tags = self.__tags[uri]
item = self.__add2db(uri, *tags, storage_type)
items.append(item)
track_ids.append(item.track_id)
self.__progress_count += 1
self.__update_progress(self.__progress_count,
self.__progress_total,
0.001)
if previous_album_id != item.album_id:
self.__notify_ui(items)
items = []
self.__notify_ui(items[notify_index:])
notify_index = len(items)
previous_album_id = item.album_id
del self.__tags[uri]
# Handle a stop request
if self.__thread is None:
raise Exception("cancelled")
self.__notify_ui(items)
return [item.track_id for item in items]
return items
def __save_streams_in_db(self, streams, storage_type):
"""
Save http stream to DB
@param streams as [str]
@param storage_type as StorageType
@return [CollectionItem]
"""
items = []
for uri in streams:
parsed = urlparse(uri)
item = self.__add2db(uri, parsed.path, parsed.netloc,
None, "", "", parsed.netloc,
parsed.netloc, "", False, 0, False, 0, 0, 0,
None, 0, "", "", "", "", 1, 0, 0, 0, 0, 0,
False, 0, storage_type)
items.append(item)
self.__progress_count += 1
return items
def __notify_ui(self, items):
"""
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment