[-]
[+]
|
Changed |
_service:tar_git:harbour-happycamper.changes
|
|
[-]
[+]
|
Changed |
_service:tar_git:harbour-happycamper.spec
^
|
|
[-]
[+]
|
Changed |
_service
^
|
@@ -2,7 +2,7 @@
<service name="tar_git">
<param name="url">https://github.com/poetaster/happycamper.git</param>
<param name="branch">main</param>
- <param name="revision">v0.1.3</param>
+ <param name="revision">v0.1.4</param>
<param name="debian">N</param>
<param name="dumb">N</param>
</service>
|
[-]
[+]
|
Changed |
_service:tar_git:harbour-happycamper-0.1.4.tar.bz2/harbour-happycamper-open-url.desktop
^
|
@@ -2,9 +2,11 @@
Type=Application
Icon=harbour-happycamper
Name=Happycamper
-MimeType=text/html;x-scheme-handler/http;x-scheme-handler/https;
+MimeType=text/html;x-scheme-handler/https;
+
X-Maemo-Service=de.poetaster.happycamper
X-Maemo-Object-Path=/de/poetaster/happycamper
X-Maemo-Method=de.poetaster.happycamper.openUrl
+
NotShowIn=X-MeeGo;
Hidden=true
|
[-]
[+]
|
Changed |
_service:tar_git:harbour-happycamper-0.1.4.tar.bz2/harbour-happycamper.pro
^
|
@@ -17,10 +17,7 @@
DISTFILES += qml/harbour-happycamper.qml \
lib/happy.py \
qml/cover/CoverPage.qml \
- qml/pages/About.qml \
- qml/pages/MainPage.qml \
- qml/pages/Settings.qml \
- qml/pages/Popup.qml \
+ qml/pages/*.qml \
lib/*.py \
rpm/harbour-happycamper.changes.in \
rpm/harbour-happycamper.changes.run.in \
@@ -37,11 +34,18 @@
INSTALLS += libs
+# extra desktop file for dbus
desktop2.path += /usr/share/applications
desktop2.files = $${TARGET}-open-url.desktop
INSTALLS += desktop2
+# extra service file for dbus
+service.path = /usr/share/dbus-1/services
+serivce.files = de.poetaster.happycamper.service
+
+INSTALLS += service
+
# to disable building translations every time, comment out the
# following CONFIG line
CONFIG += sailfishapp_i18n
|
[-]
[+]
|
Changed |
_service:tar_git:harbour-happycamper-0.1.4.tar.bz2/lib/happy.py
^
|
@@ -1,23 +1,28 @@
# -*- coding: utf-8 -*-
import pyotherside
-import time
+import sys
import os
-import shutil
-import subprocess
+import glob
from pathlib import Path
+import requests
# POETASTER
-import sys
(major, minor, micro, release, serial) = sys.version_info
sys.path.append("/usr/share/harbour-happycamper/lib/python" + str(major) + "." + str(minor) + "/site-packages/");
+from campdown.helpers import *
+from campdown.track import Track
+from campdown.album import Album
+from campdown.discography import Discography
-# check if campdown is installed
+# check if mutagen is installed
try:
- import campdown
-except ImportError:
- pyotherside.send('warningCamperNotAvailable', )
+ from mutagen.easyid3 import EasyID3
+ from mutagen.mp3 import MP3
+except ImportError as err:
+ pyotherside.send("error", "happy", "import_mutagen", format_error(err))
+
#from pydub import AudioSegment
#from pydub import effects
#from pydub.utils import mediainfo
@@ -33,17 +38,24 @@
# Functions for file operations
# #######################################################################################
-def getHomePath ():
+def get_home_path ():
homeDir = str(Path.home())
pyotherside.send('homePathFolder', homeDir )
+def format_error(err):
+ return 'ERROR: %s' % err
+def list_files(dir):
+ pathlist = Path(dir).glob('**/*.mp3')
+ for path in pathlist:
+ audio=MP3(path, ID3=EasyID3)
+ print(audio['artist'], audio['title'])
-def downLoad(url,directory):
+def download_url(url,directory):
#def __init__(self, url, out=None, verbose=False, silent=False, short=False, sleep=30, id3_enabled=True, art_enabled=True, abort_missing=False):
- downloader = campdown.Downloader(
+ downloader = Downloader(
url,
out=directory,
verbose=False,
@@ -55,10 +67,252 @@
)
try:
downloader.run()
- except :
- e = sys.exc_info()[0]
- pyotherside.send('campError', e )
-
+ except Exception as err :
+ pyotherside.send("error", "happy", "download_url", format_error(err))
+ ''' tack object attributes
+ self.title = None
+ self.artist = None
+ self.date = None
+ self.album = album
+ self.album_artist = album_artist'''
+ # save current playlist
+ playlist_items = []
+ try:
+ pl = Path(downloader.current_dir).glob('**/*.mp3')
+ for path in pl:
+ audio=MP3(path, ID3=EasyID3)
+ playlist_items.append({'media_url': 'file:///' + str(path), 'track': audio['title'], 'duration':''})
+
+ if(len(playlist_items) > 0):
+ #print(playlist_items)
+ save_playlist(downloader.current_dir + "/happycamper.pls", playlist_items)
+
+ except Exception as err:
+ print('happy save_playlist - error: ', err)
+ pyotherside.send("error", "happy", "save_playlist", format_error(err))
+ return False
+
+ # send qml side some info about the tracks and location
+ # function which needs to be called before anything else can be done.
+ #pyotherside.send('trackQueue', downloader.queue)
+ pyotherside.send('currentDir', downloader.current_dir)
pyotherside.send('downloadCompleted', "True" )
+
+def get_track_id3(path):
+ print('get_track_id3:', path, 'path:', path)
+ array = {}
+ try:
+ track_info=MP3(path, ID3=EasyID3)
+ # reduce the datastructure
+ for e in track_info:
+ array[e] = track_info[e][0]
+
+ except Exception as err:
+ print('happy track_id3 - error: ', err)
+ pyotherside.send("error", "happy", "get_track_id3", format_error(err))
+ return False
+
+ return array
+
+def save_playlist(file_name, playlist_items):
+ print('save_playlist:', file_name, 'items:', len(playlist_items))
+ try:
+ with open(file_name, 'w') as f:
+ f.write("[playlist]\n")
+ f.write("X-GNOME-Title=Happycamper\n")
+ for index, item in enumerate(playlist_items):
+ index += 1
+ f.write("File%d=%s\n" % (index, item['media_url']))
+ f.write("Title%d=%s\n" % (index, item['track']))
+ if item['duration']:
+ f.write("Length%d=%s\n" % (index, round(item['duration'] / 1000)))
+
+ f.write("NumberOfEntries=%d\n" % len(playlist_items))
+ f.write("Version=2\n\n")
+ except Exception as err:
+ print('happy save_playlist - error: ', err)
+ pyotherside.send("error", "happy", "save_playlist", format_error(err))
+ return False
+
+def get_media_folder_items(folder_path, file_types = ('*.mp3', '*.opus', '*.ogg', '*.flac')):
+ media_files = []
+ for file_type in file_types:
+ for file in glob.glob("%s/%s" % (folder_path, file_type)):
+ media_files.append(file)
+
+ media_files.sort()
+ return media_files
+
+def get_local_media( track_id = '*', video_id = '*'):
+ media_files = []
+ for file in glob.glob(self.audio_download_path + self.AUDIO_FILE_NAME.format(track_id, video_id)):
+ media_files.append(file)
+ return media_files
+
+class Downloader:
+ """
+ Main class of Campdown. This class handles all other Campdown functions and
+ executes them depending on the information it is given during initilzation.
+
+ Args:
+ url (str): Bandcamp URL to analyse and download from.
+ out (str): relative or absolute path to write to.
+ verbose (bool): sets if status messages and general information
+ should be printed. Errors are still printed regardless of this.
+ silent (bool): sets if error messages should be hidden.
+ short (bool): omits arist and album fields from downloaded track filenames.
+ sleep (number): duration between failed requests to wait for.
+ art_enabled (bool): if True the Bandcamp page's artwork will be
+ downloaded and saved alongside each of the found tracks.
+ id3_enabled (bool): if True tracks downloaded will receive new ID3 tags.
+ """
+
+ def __init__(self, url, out=None, verbose=False, silent=False, short=False, sleep=30, id3_enabled=True, art_enabled=True, abort_missing=False):
+ self.url = url
+ self.output = out
+ self.verbose = verbose
+ self.silent = silent
+ self.short = short
+ self.sleep = sleep
+ self.id3_enabled = id3_enabled
+ self.art_enabled = art_enabled
+ self.abort_missing = abort_missing
+
+ # Variables used during retrieving of information.
+ self.request = None
+ self.content = None
+
+ # this is used on the qml side to build a player
+ self.queue = [] # Queue array to store album tracks in.
+ self.current_dir = "" # current download directory
+
+ # Get the script path in case no output path is specified.
+ # self.work_path = os.path.join(
+ # os.path.dirname(os.path.abspath(__file__)), "")
+
+ self.work_path = os.path.join(os.getcwd(), "")
+
|
[-]
[+]
|
Changed |
_service:tar_git:harbour-happycamper-0.1.4.tar.bz2/qml/harbour-happycamper.qml
^
|
@@ -6,20 +6,39 @@
import "pages"
ApplicationWindow {
+ id: app
initialPage: Component { MainPage { } }
cover: Qt.resolvedUrl("cover/CoverPage.qml")
allowedOrientations: defaultAllowedOrientations
+ property bool debug: true
property var notificationObj
notificationObj: pageStack.currentPage.notification
+ property var musicFolder: StandardPaths.MusicLocation
+ property var track_volumes: {'_null': null}
+
+ signal signal_error(string module_id, string method_id, string description)
+ signal signal_media_download(var media)
+
+ MainHandler {
+ id: main_handler
+ }
+
+ NotificationsHandler {
+ id: notifications_handler
+ }
Python {
id: py
-
Component.onCompleted: {
+
+ app.track_volumes["player"] = 1.0
+
+ console.log(musicFolder)
+
addImportPath(Qt.resolvedUrl('../lib/'));
importModule('happy', function () {});
@@ -33,59 +52,69 @@
//py.deleteAllTMPFunction(tempAudioFolderPath)
});
- setHandler('warningCamperNotAvailable', function() {
- warningNoPydub = true
- });
-
- setHandler('campError', function() {
- console.log('error')
- });
-
setHandler('downloadCompleted', function() {
- //console.log('error')
notificationObj.notify("Download completed")
});
- setHandler('deletedFile', function() {
- origAudioFilePath = ""
- origAudioFileName = ""
- origAudioFolderPath = ""
- origAudioType = ""
- origAudioName = ""
- idAudioPlayer.source = ""
- idImageWaveform.source = ""
- idImageWaveformZoom.source = ""
- audioLengthSecondsPython = 0
- millisecondsPerPixelPython = 0
- showTools = false
- });
setHandler('copiedToClipboard', function() {
clipboardAvailable = true
});
- }
- // file operations
- function download(url,dir) {
- call("happy.downLoad", [url,dir])
+ setHandler('trackQueue', function(queue) {
+ if(debug) console.log(queue)
+ });
+ setHandler('currentDir', function(dir) {
+ musicFolder = dir
+ const local_media = py.get_media_folder_items(dir)
+ for (var i = 0; i < local_media.length; i++) {
+ if (debug) console.log('folder_picker_page - media:', local_media[i])
+ const track_info = py.get_track_id3(local_media[i])
+ main_handler.add_playlist_item(local_media[i],track_info)
+ }
+ main_handler.player_artwork = dir + '/cover.jpg'
+ if (debug) console.log(dir)
+ });
+
+ setHandler('error', error_handler);
+
}
- function getHomePath() {
- call("happy.getHomePath", [])
+
+ function error_handler(module_id, method_id, description) {
+ console.log('Module ERROR - source:', module_id, method_id, 'error:', description);
+ //app.signal_error(module_id, method_id, description);
}
- function deleteFile() {
- stopPlayingResetWaveform()
- py.deleteAllTMPFunction()
- call("happy.deleteFile", [ origAudioFilePath ])
- }
- function renameOriginal() {
- stopPlayingResetWaveform()
- py.deleteAllTMPFunction()
- var newFilePath = origAudioFolderPath + idFilenameRenameText.text + "." + origAudioType
- var newFileName = idFilenameRenameText.text
- var newFileType = origAudioType
- call("happy.renameOriginal", [ origAudioFilePath, newFilePath, newFileName, newFileType ])
+ // file operations
+ function download_url(url,dir) {
+ call("happy.download_url", [url,dir])
}
+ function get_media_folder_items(folder_path) {
+ return call_sync('happy.get_media_folder_items', [folder_path]);
+ }
+
+ function get_local_media(track_id, video_id) {
+ var params = []
+ if (track_id) {
+ params.push(track_id)
+ if (video_id) params.push(video_id)
+ }
+ return call_sync('happy.get_local_media', params);
+ }
+ /* this is borked for obvious reasons */
+ function get_track_cache(media_url) {
+ return call_sync('happy.get_track_info', [media_url])
+ }
+ // this is a fint to get track info using ID3 tags.
+ function get_track_id3(media_url) {
+ return call_sync('happy.get_track_id3', [media_url])
+ }
+ function save_playlist(file_name, playlist_items) {
+ return call_sync('happy.save_playlist', [file_name, playlist_items])
+ }
+ function get_home_path() {
+ call("happy.get_home_path", [])
+ }
onError: {
// when an exception is raised, this error handler will be called
console.log('python error: ' + traceback);
|
[-]
[+]
|
Added |
_service:tar_git:harbour-happycamper-0.1.4.tar.bz2/qml/pages/CachedImage.qml
^
|
@@ -0,0 +1,54 @@
+import QtQuick 2.2
+import Sailfish.Silica 1.0
+
+Image {
+ id: image_item
+
+ property string remote_source
+ property string local_source
+ property bool preview: false
+
+ Timer {
+ id: image_saver_timer
+ interval: 1000
+ running: false
+ repeat: false
+ onTriggered: {
+ image_item.grabToImage(function(result) {
+ console.log('Saving image to cache - image:', local_source)
+ result.saveToFile(local_source);
+ }, image_item.sourceSize);
+ }
+ }
+
+ onStatusChanged: {
+ if (status == Image.Ready) {
+ if ((source == remote_source || source == remote_source+'/preview') && local_source.length > 0) {
+ image_saver_timer.start()
+ }
+ } else if (status == Image.Error) {
+ console.log('Image - could not load:', source)
+ if (source == local_source || source == 'file://'+local_source) {
+ if (preview) source = remote_source + '/preview'
+ else source = remote_source
+ console.log('Image - fetching remote image:', source)
+ }
+ }
+ }
+
+ onRemote_sourceChanged: {
+ if (!remote_source) return;
+
+ if (preview) local_source = StandardPaths.cache + '/preview_' + basename(remote_source)
+ else local_source = StandardPaths.cache + '/' + basename(remote_source)
+ source = local_source
+ }
+
+ Component.onCompleted: {
+
+ }
+
+ function basename(file_path) {
+ return (file_path.slice(file_path.lastIndexOf("/")+1))
+ }
+}
|
[-]
[+]
|
Added |
_service:tar_git:harbour-happycamper-0.1.4.tar.bz2/qml/pages/MainHandler.qml
^
|
@@ -0,0 +1,277 @@
+import QtQuick 2.0
+import Sailfish.Silica 1.0
+import QtMultimedia 5.6
+import Amber.Mpris 1.0
+
+Item {
+ id: main_handler
+
+ property string player_media_file
+ property string player_track_id
+ property string player_track_name
+ property string player_album_id
+ property string player_album_name
+ property string player_artist_id
+ property string player_artist_name
+ property string player_artwork
+
+ property alias audio_player: audio_player_item
+ property alias playlist: playlist_item
+ property var tracks_info: {'_null': null}
+
+ property bool player_available: playlist_item.itemCount > 0
+
+ property double player_volume: 1
+ property double track_volume: 1
+
+ Audio {
+ id: audio_player_item
+ //source: player_media_file
+
+ playlist: Playlist {
+ id: playlist_item
+ playbackMode: Playlist.Sequential
+
+ onCurrentItemSourceChanged: {
+ console.log('CurrentItemSourceChanged:', currentItemSource)
+ const track_id = media_file_to_track(currentItemSource)
+ var track_info = tracks_info[track_id] || tracks_info[currentItemSource]
+
+ // shortcut vis id3 method
+ //if (!track_info) track_info = get_track_id3(currentItemSource);
+ if (!track_info && track_id) track_info = get_track_info(track_id);
+ if (!track_info) return;
+
+ player_track_id = track_id || 0
+ player_track_name = track_info.track || ''
+ player_album_name = track_info.album || ''
+ player_artist_name = track_info.artist || ''
+ player_artwork = track_info.artwork || ''
+ track_volume = app.track_volumes[track_id] || 1
+ player_volume = app.track_volumes["player"] || 1
+ }
+
+ onItemInserted: function(start_index, end_index) {
+ console.log('ItemInserted:', start_index, end_index, currentIndex)
+ if (currentIndex > -1 && (start_index < currentIndex || end_index > currentIndex)) return;
+
+ for (var i = start_index; i <= end_index; i++) {
+ if (currentIndex == -1 || i == 0) {
+ const track_id = media_file_to_track(itemSource(i))
+ console.log('ItemInserted track_id:', track_id)
+ if (!track_id) return;
+ const track_info = get_track_info(track_id)
+ console.log('ItemInserted track_info:', track_info)
+ if (!track_info) return;
+ //player_artwork = track_info.artwork
+ break
+ }
+ }
+ }
+
+ onItemRemoved: function(start_index, end_index) {
+ if (itemCount == 0) {
+ player_track_id = 0
+ player_track_name = ''
+ player_album_name = ''
+ player_artist_name = ''
+ player_artwork = ''
+ track_volume = 1
+ }
+ }
+
+ onLoadFailed: function() {
+ console.log('loadFailed ERROR:', error, '/', errorString);
+ }
+
+ onLoaded: function() {
+
+ }
+ }
+
+ onPlaying: {
+ mpris.playbackStatus = Mpris.Playing
+ }
+
+ onPaused: {
+ mpris.playbackStatus = Mpris.Paused
+ }
+
+ onStopped: {
+ mpris.playbackStatus = Mpris.Stopped
+ }
+
+ onDurationChanged: {
+ const track_info = tracks_info[media_file_to_track(playlist_item.currentItemSource)] || tracks_info[playlist_item.currentItemSource]
+ if (!track_info || track_info.duration) return;
+ track_info.duration = duration
+ }
+ }
+
+ MprisPlayer {
+ id: mpris
+
+ serviceName: "happycamper"
+ identity: "Happy Camper"
+ supportedUriSchemes: ["file"]
+ supportedMimeTypes: ["audio/x-wav", "audio/mpeg", "audio/x-vorbis+ogg"]
+
+ canControl: true
+ canGoNext: playlist_item.itemCount > 0
+ canGoPrevious: playlist_item.itemCount > 0 || audio_player_item.position > 1
+ canPause: true
+ canPlay: playlist_item.itemCount > 0
+ canSeek: false
+ canQuit: false
+ canRaise: false
+ hasTrackList: true
+ playbackStatus: Mpris.Stopped
+ loopStatus: Mpris.LoopNone
+ shuffle: false
+ volume: 1.0
+
+ onPauseRequested: {
+ audio_player_item.pause();
+ }
+
+ onPlayRequested: {
+ audio_player_item.play()
+ }
+
+ onPlayPauseRequested: {
+ if (audio_player_item.playbackState === Audio.PlayingState) audio_player_item.pause();
+ else audio_player_item.play();
+ }
+
+ onStopRequested: {
+ audio_player_item.stop();
+ }
+
+ onNextRequested: {
+ console.log('mpris next')
+ playlist.next()
+ }
+
+ onPreviousRequested: {
+ console.log('mpris previous')
+ if (audio_player_item.position > 5000) audio_player_item.seek(0)
+ else playlist.previous()
+ }
+ }
+
+ onPlayer_media_fileChanged: {
+ mpris.canSeek = audio_player_item.seekable
+ }
+
+ onPlayer_track_nameChanged: {
+ mpris.metaData.title = player_track_name
+ console.log('mpris track:', player_track_name)
+ }
+
+ onPlayer_album_nameChanged: {
+ mpris.metaData.albumTitle = player_album_name
+ }
+
+ onPlayer_artist_nameChanged: {
+ mpris.metaData.contributingArtist = player_artist_name
+ console.log('mpris artist:', player_artist_name)
+ }
+
+ onPlayer_artworkChanged: {
+ mpris.metaData.artUrl = player_artwork
+ console.log('mpris artUrl:', player_artwork)
+ }
+
+ onPlayer_volumeChanged: {
+ main_handler.audio_player.volume = player_volume * track_volume
+ }
+
+ onTrack_volumeChanged: {
+ main_handler.audio_player.volume = player_volume * track_volume
+ }
+
+ function seconds_to_minutes_seconds(total_seconds) {
+ if (isNaN(total_seconds)) return "00:00"
+ var minutes = Math.floor(total_seconds / 60)
+ var seconds = Math.floor(total_seconds % 60)
+
+ return minutes + ":" + ("00" + seconds).slice(-2)
+ }
|
[-]
[+]
|
Changed |
_service:tar_git:harbour-happycamper-0.1.4.tar.bz2/qml/pages/MainPage.qml
^
|
@@ -55,11 +55,14 @@
}
MenuItem {
+ text: qsTr("Player")
+ onClicked: pageStack.animatorPush(Qt.resolvedUrl("PlayerPage.qml"))
+ }
+ MenuItem {
text: qsTr("Download")
onClicked: {
notification.notify("Download starting...")
- py.download(webview.url.toString(),'/home/defaultuser/Music/')
-
+ py.download_url(webview.url.toString(),'/home/defaultuser/Music/')
}
}
}
@@ -69,7 +72,6 @@
onClicked:
if (webview.canGoForward)
webview.goForward()
-
}
}
Popup {
|
[-]
[+]
|
Added |
_service:tar_git:harbour-happycamper-0.1.4.tar.bz2/qml/pages/NotificationsHandler.qml
^
|
@@ -0,0 +1,97 @@
+import QtQuick 2.0
+import Nemo.Notifications 1.0
+import Sailfish.Silica 1.0
+
+Item {
+ id: notifications_handler
+
+ property var notifications_by_file_name: {'_null': null}
+ property int cache_progress_id
+
+ Notice {
+ id: system_notice
+ duration: Notice.Long
+ text: "Info"
+ }
+
+ Notification {
+ id: download_notification
+ appIcon: "harbour-musicex"
+ appName: "Music Explorer"
+ expireTimeout: 30000
+ urgency: Notification.Low
+ onClosed: {
+ console.log('download notification closed - reason:', reason, 'id:', replacesId);
+ }
+ }
+
+ Component.onCompleted: {
+ app.signal_error.connect(error_handler)
+ app.signal_media_download.connect(media_download)
+ //app.signal_cache_rebuild.connect(cache_rebuild)
+ }
+
+ Component.onDestruction: {
+ app.signal_error.disconnect(error_handler)
+ app.signal_media_download.disconnect(media_download)
+ app.signal_cache_rebuild.disconnect(cache_rebuild)
+ }
+
+ function error_handler(module_id, method_id, description) {
+ console.log('error_handler - source:', module_id, method_id, 'error:', description);
+ system_notice.text = description
+ system_notice.show()
+ }
+
+ function media_download(details) {
+ //system_notice.text = 'Media download ' + details.status
+ //system_notice.show()
+
+ download_notification.summary = 'Media download ' + details.status
+ download_notification.subText = String(details.file_name)
+ download_notification.body = String(details.file_name)
+ if (details.status == 'start') {
+ download_notification.progress = 0.0
+ download_notification.expireTimeout = 30000
+ } else if (details.status == 'fail') {
+ download_notification.subText = String(details.reason)
+ download_notification.expireTimeout = 1000
+ system_notice.text = details.reason
+ system_notice.show()
+ } else if (details.status == 'complete') {
+ download_notification.progress = 1.0
+ download_notification.expireTimeout = 1000
+ notifications_by_file_name[details.file_name] = 0
+ }
+ else if (details.status == 'progress') download_notification.progress = details.percent / 100
+ if (notifications_by_file_name[details.file_name]) {
+ download_notification.replacesId = notifications_by_file_name[details.file_name]
+ }
+ if (details.thumbnail_url) download_notification.icon = details.thumbnail_url
+ download_notification.publish()
+ notifications_by_file_name[details.file_name] = download_notification.replacesId
+ }
+
+ function cache_rebuild(details) {
+ download_notification.summary = 'Rebuilding cache... '
+ download_notification.subText = ""
+ download_notification.body = ""
+ if (details.status == 'start') {
+ download_notification.progress = 0.0
+ download_notification.expireTimeout = 30000
+ } else if (details.status == 'fail') {
+ download_notification.subText = String(details.reason)
+ download_notification.expireTimeout = 1000
+ system_notice.text = details.reason
+ system_notice.show()
+ } else if (details.status == 'complete') {
+ download_notification.progress = 1.0
+ download_notification.expireTimeout = 1000
+ }
+ else if (details.status == 'progress') download_notification.progress = details.percent / 100
+ if (details.thumbnail_url) download_notification.icon = details.thumbnail_url
+ download_notification.replacesId = cache_progress_id
+ download_notification.publish()
+ cache_progress_id = download_notification.replacesId
+ }
+}
|
[-]
[+]
|
Added |
_service:tar_git:harbour-happycamper-0.1.4.tar.bz2/qml/pages/PlayerPage.qml
^
|
@@ -0,0 +1,304 @@
+import QtQuick 2.2
+import Sailfish.Silica 1.0
+import QtMultimedia 5.5
+import Sailfish.Pickers 1.0
+
+Page {
+ id: lyrics_page
+
+ anchors.fill: parent
+
+ property string default_playlist_file: StandardPaths.music + '/happycamper/playlist.pls'
+
+ SilicaListView {
+ id: list_view
+
+ width: parent.width
+
+ anchors {
+ top: parent.top
+ bottom: player_controls_item.top
+ }
+
+ PullDownMenu {
+ MenuItem {
+ text: "Playback settings"
+ enabled: true
+ onClicked: {
+ pageStack.push("VolumePage.qml", {})
+ }
+ }
+
+ MenuItem {
+ visible: true
+ text: "Load folder"
+ onClicked: {
+ pageStack.push(folder_picker_page)
+ }
+ }
+
+ MenuItem {
+ text: "Clear playlist"
+ enabled: main_handler.playlist.itemCount > 0
+ onClicked: {
+ //var remorse = Remorse.popupAction(list_view, "Clear playlist", function() { })
+ main_handler.audio_player.stop()
+ main_handler.playlist.clear()
+ }
+ }
+
+ /*MenuItem {
+ visible: false
+ text: "Load all audio"
+ onClicked: {
+ main_handler.audio_player.stop()
+ main_handler.playlist.clear()
+ const local_media = py.get_local_media()
+ for (var i = 0; i < local_media.length; i++) {
+ main_handler.add_playlist_item(local_media[i])
+ }
+ }
+ }*/
+
+ MenuItem {
+ visible: true
+ text: "Load playlist"
+ onClicked: {
+ pageStack.push(file_picker_page)
+ }
+ }
+
+ MenuItem {
+ visible: true
+ enabled: main_handler.playlist.itemCount > 0
+ text: "Save playlist"
+ onClicked: {
+ var playlist_items = []
+ for (var i = 0; i < main_handler.playlist.itemCount; i++) {
+ const media_url = String(main_handler.playlist.itemSource(i))
+ const track_id = main_handler.media_file_to_track(media_url)
+ var track_info = main_handler.tracks_info[track_id]
+ if (!track_info) continue
+ track_info.media_url = media_url
+ playlist_items.push(track_info)
+ }
+ console.log(py.save_playlist(default_playlist_file, playlist_items))
+ }
+ }
+ }
+
+ header: Item {
+ id: list_header
+
+ width: lyrics_page.width
+ height: Theme.paddingLarge + album_thumb.height
+
+ CachedImage {
+ id: album_thumb
+ width: lyrics_page.width
+ visible: main_handler.player_artwork && main_handler.player_artwork.length && (album_thumb.status == Image.Ready || album_thumb.status == Image.Loading)
+ height: visible ? lyrics_page.width : 0
+ fillMode: Image.PreserveAspectCrop
+ remote_source: main_handler.player_artwork
+ }
+ }
+
+ model: main_handler.playlist
+
+ delegate: PlayerTrackItem {
+
+ }
+
+ ViewPlaceholder {
+ enabled: !main_handler.player_available
+ text: "No media"
+ hintText: "Add media to enable playback."
+ }
+ }
+
+ Item {
+ id: player_controls_item
+
+ Rectangle {
+ id: background_rectangle
+ anchors.fill: parent
+ color: Theme.rgba(Theme.highlightBackgroundColor, Theme.highlightBackgroundOpacity)
+ gradient: Gradient {
+ GradientStop {
+ position: 0.0
+ color: 'transparent'
+ }
+ GradientStop {
+ position: 0.8
+ color: background_rectangle.color
+ }
+ }
+ }
+
+ width: lyrics_page.width
+ height: childrenRect.height + (Theme.paddingLarge * 3)
+
+ anchors {
+ bottom: parent.bottom
+ }
+
+ IconButton {
+ id: play_button
+ icon.source: "image://theme/icon-m-play"
+ onClicked: {
+ main_handler.audio_player.play()
+ }
+ enabled: main_handler.player_available
+ visible: main_handler.audio_player.playbackState != Audio.PlayingState
+ anchors {
+ bottom: parent.bottom
+ horizontalCenter: parent.horizontalCenter
+ bottomMargin: Theme.paddingLarge
+ }
+ }
+
+ IconButton {
+ id: pause_button
+ icon.source: "image://theme/icon-m-pause"
+ onClicked: {
+ main_handler.audio_player.pause()
+ }
+ enabled: main_handler.player_available
+ visible: !play_button.visible
+ anchors {
+ centerIn: play_button
+ }
+ }
+
+ IconButton {
+ id: previous_button
+ icon.source: "image://theme/icon-m-previous"
+ onClicked: {
+ if (main_handler.audio_player.position > 5000) main_handler.audio_player.seek(0)
+ else main_handler.playlist.previous()
+ }
+ enabled: main_handler.player_available && (main_handler.playlist.currentIndex > 0 || main_handler.audio_player.position > 1)
+ anchors {
+ verticalCenter: play_button.verticalCenter
+ right: play_button.left
+ rightMargin: parent.width / 5
+ }
+ }
+
+ IconButton {
+ id: next_button
+ icon.source: "image://theme/icon-m-next"
+ onClicked: {
+ //main_handler.audio_player.seek(main_handler.audio_player.duration)
+ main_handler.playlist.next()
+ }
+ enabled: main_handler.player_available
+ anchors {
+ verticalCenter: play_button.verticalCenter
+ left: play_button.right
+ leftMargin: parent.width / 5
|
[-]
[+]
|
Added |
_service:tar_git:harbour-happycamper-0.1.4.tar.bz2/qml/pages/PlayerTrackItem.qml
^
|
@@ -0,0 +1,166 @@
+import QtQuick 2.2
+import Sailfish.Silica 1.0
+import QtMultimedia 5.5
+
+ListItem {
+ id: list_item
+ property var track_info
+ property string track_name
+ property string album_name
+ property string artist_name
+ property int duration
+
+ height: Theme.itemSizeMedium + context_menu.height
+
+ Label {
+ id: duration_label
+ width: parent.width / 6
+ visible: true
+ text: main_handler.seconds_to_minutes_seconds(duration / 1000)
+ color: index == main_handler.playlist.currentIndex ? Theme.highlightColor : Theme.primaryColor
+ font.pixelSize: Theme.fontSizeLarge
+ horizontalAlignment: Text.AlignRight
+ anchors {
+ top: parent.top
+ leftMargin: Theme.paddingMedium
+ }
+ }
+
+ Label {
+ id: title_label
+ color: index == main_handler.playlist.currentIndex ? Theme.highlightColor : Theme.primaryColor
+ text: track_name
+ font.pixelSize: Theme.fontSizeMedium
+ truncationMode: TruncationMode.Fade
+ fontSizeMode: Text.Fit
+ minimumPixelSize: Theme.fontSizeExtraSmall
+ horizontalAlignment: Text.AlignCenter
+ anchors {
+ top: parent.top
+ left: duration_label.right
+ right: parent.right
+ leftMargin: Theme.paddingMedium
+ rightMargin: Theme.paddingMedium
+ }
+ }
+
+ Label {
+ id: artist_label
+ visible: Boolean(artist_name)
+ color: index == main_handler.playlist.currentIndex ? Theme.highlightColor : Theme.primaryColor
+ text: artist_name
+ font.pixelSize: Theme.fontSizeExtraSmall
+ truncationMode: TruncationMode.Fade
+ horizontalAlignment: Text.AlignCenter
+ anchors {
+ top: title_label.bottom
+ left: duration_label.right
+ right: parent.right
+ leftMargin: Theme.paddingMedium
+ rightMargin: Theme.paddingMedium
+ }
+ }
+
+ menu: ContextMenu {
+ id: context_menu
+
+ MenuItem {
+ visible: true
+ text: "Remove from playlist"
+ onClicked: {
+ main_handler.playlist.removeItem(index)
+ }
+ }
+ }
+
+ onClicked: {
+ main_handler.playlist.currentIndex = index
+ main_handler.audio_player.play()
+ }
+
+ Audio {
+ id: audio_preloader
+
+ onDurationChanged: {
+ list_item.duration = duration
+ track_info.duration = duration
+ console.log('audio_preloader duration:', duration)
+ audio_preloader.destroy()
+ }
+
+ metaData.onTitleChanged: {
+ if (!metaData.title) return;
+ console.log('audio_preloader metadata track:', metaData.title, 'duration:', duration)
+ track_name = metaData.title
+ track_info.track = metaData.title
+ }
+
+ metaData.onAlbumTitleChanged: {
+ if (!metaData.albumTitle) return;
+ console.log('audio_preloader metadata album:', metaData.albumTitle)
+ album_name = metaData.albumTitle
+ track_info.album = metaData.albumTitle
+ }
+
+ metaData.onContributingArtistChanged: {
+ if (!metaData.contributingArtist) return;
+ console.log('audio_preloader metadata contributingArtist:', metaData.contributingArtist)
+ artist_name = metaData.contributingArtist
+ track_info.artist = metaData.contributingArtist
+ }
+
+ metaData.onAlbumArtistChanged: {
+ if (!metaData.albumArtist) return;
+ console.log('audio_preloader metadata albumArtist:', metaData.albumArtist)
+ artist_name = metaData.albumArtist
+ track_info.artist = metaData.albumArtist
+ }
+
+ metaData.onCoverArtUrlLargeChanged: {
+ if (!metaData.coverArtUrlLarge) return;
+ console.log('audio_preloader metadata coverArtUrlLarge:', metaData.coverArtUrlLarge)
+ track_info.artwork = metaData.coverArtUrlLarge
+ }
+
+ metaData.onCoverArtUrlSmallChanged: {
+ if (!metaData.coverArtUrlSmall || track_info.artwork) return;
+ console.log('audio_preloader metadata coverArtUrlSmall:', metaData.coverArtUrlSmall)
+ track_info.artwork = metaData.coverArtUrlSmall
+ }
+ }
+
+ Component.onCompleted: {
+ const track_id = main_handler.media_file_to_track(source)
+ track_info = main_handler.tracks_info[track_id] || main_handler.tracks_info[source]
+
+
+ if (track_id && !track_info) track_info = main_handler.get_track_info(track_id)
+ if (!track_info) {
+ // try using our hack.
+ const path = source.toString()
+ const info = py.get_track_id3(path.slice(7))
+
+ track_info = {
+ 'track': main_handler.basename(source),
+ 'album': '',
+ 'artist': '',
+ 'artwork': main_handler.player_artwork,
+ 'duration': null,
+ }
+ if (info['title']) track_info['track'] = info['title']
+ if (info['album']) track_info['album'] = info['album']
+ if (info['artist']) track_info['artist'] = info['artist']
+
+ if (track_id) main_handler.tracks_info[track_id] = track_info
+ else main_handler.tracks_info[source] = track_info
+ }
+
+ artist_name = track_info.artist
+ track_name = track_info.track
+ if (track_info.duration) duration = track_info.duration
+
+ console.log('loading source data:', source, 'track:', track_id, 'name:', track_info.track)
+
+ if (!duration) audio_preloader.source = source
+ }
+}
|
[-]
[+]
|
Added |
_service:tar_git:harbour-happycamper-0.1.4.tar.bz2/qml/pages/VolumePage.qml
^
|
@@ -0,0 +1,145 @@
+import QtQuick 2.2
+import Sailfish.Silica 1.0
+import QtMultimedia 5.6
+
+Page {
+ id: volume_page
+
+ anchors.fill: parent
+
+ SilicaFlickable {
+ id: flickable
+
+ anchors.fill: parent
+
+ VerticalScrollDecorator {
+ flickable: flickable
+ }
+
+ Column {
+ width: parent.width
+ height: parent.height
+
+ SectionHeader {
+ text: "Shuffle and repeat"
+ }
+
+ Row {
+ id: row
+ width: parent.width
+ height: Theme.itemSizeLarge
+
+ Switch {
+ width: parent.width / 2
+ anchors.bottom: parent.bottom
+ icon.source: "image://theme/icon-m-shuffle"
+ automaticCheck: false
+ checked: main_handler.playlist.playbackMode == Playlist.Random
+ onClicked: {
+ if (checked) main_handler.playlist.playbackMode = Playlist.Sequential
+ else main_handler.playlist.playbackMode = Playlist.Random
+ }
+ }
+
+ Switch {
+ width: parent.width / 2
+ anchors.bottom: parent.bottom
+ icon.source: main_handler.playlist.playbackMode == Playlist.CurrentItemInLoop ? "image://theme/icon-m-repeat-single" : "image://theme/icon-m-repeat"
+ automaticCheck: false
+ checked: main_handler.playlist.playbackMode == Playlist.CurrentItemInLoop || main_handler.playlist.playbackMode == Playlist.Loop
+
+ onClicked: {
+ console.log('checked:', checked, 'mode:',main_handler.playlist.playbackMode )
+ if (checked) {
+ if (main_handler.playlist.playbackMode == Playlist.Loop) main_handler.playlist.playbackMode = Playlist.CurrentItemInLoop
+ else main_handler.playlist.playbackMode = Playlist.Sequential
+ } else {
+ main_handler.playlist.playbackMode = Playlist.Loop
+ }
+ }
+ }
+ }
+
+ SectionHeader {
+ text: "Playback rate"
+ }
+
+ Slider {
+ id: playback_rate
+ width: parent.width
+
+ stepSize: 0.1
+ minimumValue: 0.5
+ maximumValue: 2.0
+ handleVisible: true
+ valueText: Math.round(sliderValue * 100) + ' %'
+ value: main_handler.audio_player.playbackRate
+
+ onReleased: {
+ console.log('Playback rate:', sliderValue)
+ main_handler.audio_player.playbackRate = sliderValue
+ }
+ }
+
+ SectionHeader {
+ text: "Output volume"
+ }
+
+ ProgressBar {
+ width: parent.width
+ minimumValue: 0.0
+ maximumValue: 1.0
+ value: main_handler.audio_player.volume
+ valueText: Math.round(main_handler.audio_player.volume * 100) + ' %'
+ }
+
+ SectionHeader {
+ text: "Player volume"
+ }
+
+ Slider {
+ id: player_volume_slider
+ width: parent.width
+
+ stepSize: 0.05
+ minimumValue: 0.0
+ maximumValue: 1.0
+ handleVisible: true
+ valueText: Math.round(sliderValue * 100) + ' %'
+ value: main_handler.player_volume
+
+ onPositionChanged: {
+ main_handler.player_volume = sliderValue
+ app.track_volumes["player"] = sliderValue
+ }
+ }
+
+ SectionHeader {
+ text: "Track volume"
+ }
+
+ Slider {
+ id: track_volume_slider
+ width: parent.width
+
+ stepSize: 0.05
+ minimumValue: 0.0
+ maximumValue: 2.0
+ handleVisible: enabled
+ valueText: Math.round(sliderValue * 100) + ' %'
+ value: main_handler.track_volume
+ label: main_handler.player_track_name
+ enabled: Boolean(main_handler.player_track_id > 0)
+
+ onPositionChanged: {
+ main_handler.track_volume = sliderValue
+ app.track_volumes[String(main_handler.player_track_id)] = sliderValue
+ }
+ }
+ }
+
+ Component.onCompleted: {
+
+ }
+ }
+}
|
[-]
[+]
|
Changed |
_service:tar_git:harbour-happycamper-0.1.4.tar.bz2/translations/harbour-happycamper-de.ts
^
|
@@ -53,6 +53,10 @@
<source>About</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>Player</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>Settings</name>
|
[-]
[+]
|
Changed |
_service:tar_git:harbour-happycamper-0.1.4.tar.bz2/translations/harbour-happycamper.ts
^
|
@@ -53,6 +53,10 @@
<source>About</source>
<translation type="unfinished"></translation>
</message>
+ <message>
+ <source>Player</source>
+ <translation type="unfinished"></translation>
+ </message>
</context>
<context>
<name>Settings</name>
|