3 Komitmen 94d352263c ... c08ca3b346

Pembuat SHA1 Pesan Tanggal
  chaosmonk c08ca3b346 allow saving tracks 5 tahun lalu
  chaosmonk e421b202bf allow saving tracks 5 tahun lalu
  chaosmonk 1464ba1d87 cleanup 5 tahun lalu
6 mengubah file dengan 106 tambahan dan 77 penghapusan
  1. 3 3
      channels.py
  2. 25 2
      library.py
  3. 27 59
      libricia-music
  4. 23 12
      libricia-music.glade
  5. 5 1
      playback.py
  6. 23 0
      settings.py

+ 3 - 3
channels.py

@@ -22,7 +22,7 @@ import library
 
 class Channel:
     def __init__(self, musicbrainz_artist_data):
-        self.library = library.Library()
+        self.pool = library.Library()
 
         url = 'https://api.jamendo.com/v3.0/tracks/?'
 
@@ -47,7 +47,7 @@ class Channel:
         ]
 
         for result in requests.get(url + '&'.join(['='.join(option) for option in xartist_options])).json()['results']:
-            self.library.add_jamendo_track(result)
+            self.pool.add_jamendo_track(result)
 
         for result in requests.get(url + '&'.join(['='.join(option) for option in tags_options])).json()['results']:
-            self.library.add_jamendo_track(result)
+            self.pool.add_jamendo_track(result)

+ 25 - 2
library.py

@@ -15,17 +15,34 @@
 #    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 #
 
+import yaml
+
 import gi
 gi.require_version('Gtk', '3.0')
 from gi.repository import Gtk
 
 import misc
+import settings
 
 
 class Library(dict):
     def __init__(self):
         self['artists'] = {}
 
+    @classmethod
+    def load(cls):
+        library = cls()
+        try:
+            with open(settings.config_dir + '/library.yaml', 'r') as f:
+                library.update(yaml.safe_load(f))
+            return library
+        except:
+            return library
+
+    def save(self):
+        with open(settings.config_dir + '/library.yaml', 'w') as f:
+            yaml.dump(dict(self), f)
+
     def add_artist(self, artist_id, artist_data):
         if artist_id not in self['artists']:
             self['artists'][artist_id] = artist_data
@@ -42,6 +59,13 @@ class Library(dict):
         if track_id not in self['artists'][artist_id]['albums'][album_id]['tracks']:
             self['artists'][artist_id]['albums'][album_id]['tracks'][track_id] = track_data
 
+    def remove_track(self, ID):
+        for artist_id, artist_data in self['artists'].items():
+            for album_id, album_data in artist_data['albums'].items():
+                for track_id, track_data in album_data['tracks'].items():
+                    if track_id == ID:
+                        return self['artists'][artist_id]['albums'][album_id]['tracks'].pop(track_id)
+
     def add_jamendo_track(self, data):
         artist_id = 'jamendo:' + data['artist_id']
         artist_data = {
@@ -67,8 +91,7 @@ class Library(dict):
             for album_id, album_data in artist_data['albums'].items():
                 for track_id, track_data in album_data['tracks'].items():
                     if track_id == ID:
-                        return track_id, track_data
-        return None
+                        return track_id, track_data, album_id, album_data, artist_id, artist_data
 
     def populate_liststore(self, liststore):
         for artist_id, artist_data in self['artists'].items():

+ 27 - 59
libricia-music

@@ -22,63 +22,13 @@ import requests
 
 import gi
 gi.require_version('Gtk', '3.0')
-gi.require_version('Gst', '1.0')
-from gi.repository import Gtk, Gst, Gdk, GLib
-
-import musicbrainzngs
-musicbrainzngs.set_useragent('Libricia', '0.x')
+from gi.repository import Gtk, Gdk, GLib
 
 import channels
+import library
 import playback
 import search
 
-JAMENDO_BASE_URL = 'https://api.jamendo.com/v3.0/tracks/?'
-
-JAMENDO_OPTIONS = [
-    ('client_id', '8e4fe531'),
-    ('format', 'jsonpretty'),
-    ('ccnc', '0'),
-    ('ccnd', '0'),
-]
-
-
-class Artist():
-    def __init__(self, data):
-        self.data = data
-
-    def get_tags(self, count_threshold):
-        tags = sorted(self.data['tag-list'],
-                      key=lambda k: k['count'], reverse=True)
-        return [tag['name'] for tag in tags if int(tag['count']) > count_threshold]
-
-
-class Channel():
-    def __init__(self, artist):
-        self.tags = artist.get_tags(0)
-        self.pool = set()
-        self.get_tracks_similar_to_artist(artist, 25)
-        self.playlist = []
-
-    def get_tracks_similar_to_artist(self, artist, num_results):
-        by_xartist = JAMENDO_OPTIONS + [
-            ('limit', str(num_results)),
-            ('xartist', artist.data['name']),
-        ]
-
-        by_tags = JAMENDO_OPTIONS + [
-            ('limit', str(num_results)),
-            ('fuzzytags', '+'.join(artist.get_tags(0))),
-        ]
-
-        search_results = (requests.get(JAMENDO_BASE_URL + '&'.join(['='.join(option) for option in by_xartist])).json()[
-                          'results'] + requests.get(JAMENDO_BASE_URL + '&'.join(['='.join(option) for option in by_tags])).json()['results'])
-
-        for result in search_results:
-            self.pool.add(playback.Track.new_from_jamendo_data(result))
-
-    def get_track(self):
-        return random.sample(self.pool, 1)[0]
-
 
 class App:
     def __init__(self):
@@ -94,17 +44,26 @@ class App:
         self.timestamp = self.builder.get_object("timestamp")
         self.playliststore = Gtk.ListStore()
         self.libraryliststore = self.builder.get_object("libraryliststore")
+        self.savebutton = self.builder.get_object("savebutton")
 
         self.playback_bar_is_pressed = False
         self.player = playback.Player()
         GLib.timeout_add(250, self.update_gui)
         self.search = search.Search()
+        self.permanent_library = library.Library.load()
+        self.temporary_library = library.Library.load()
+        self.temporary_library.populate_liststore(self.libraryliststore)
 
         self.window.show_all()
 
     def update_gui(self):
         self.player.update_gui(self.libraryliststore, self.playback_bar,
                                self.playback_bar_is_pressed, self.timestamp, self.playpause_button)
+        now_playing = self.player.get_now_playing()
+        if now_playing is None or self.permanent_library.get_track(now_playing) is None:
+            self.savebutton.set_active(False)
+        else:
+            self.savebutton.set_active(True)
         return True
 
     def on_search_clicked(self, button):
@@ -121,16 +80,12 @@ class App:
         if len(self.search.results) > 0:
             self.searchpopup.popdown()
             self.channel = channels.Channel(self.search.results[0])
-            self.channel.library.populate_liststore(self.libraryliststore)
+            self.temporary_library.update(self.channel.pool)
+            self.temporary_library.populate_liststore(self.libraryliststore)
 
     def on_search_row_activated(self, treeview, path, column):
         self.searchpopup.popdown()
         channel = Channel(self.search.results[path.get_indices()[0]])
-        playlist = playback.Playlist(self.playliststore)
-        for i in range(4):
-            track = channel.get_track()
-            playlist.append_track(track)
-        self.player.set_playlist(playlist)
 
     def on_play_pause(self, button):
         if self.player.is_playing:
@@ -171,13 +126,26 @@ class App:
 
     def on_track_activated(self, treeview, path, column):
         treeiter = self.libraryliststore.get_iter(path)
-        track_id, track_data = self.channel.library.get_track(
+        track_id, track_data, album_id, album_data, artist_id, artist_data = self.temporary_library.get_track(
             self.libraryliststore.get_value(treeiter, 0))
         if track_id is not None:
             self.player.queue_track(track_id, track_data)
             self.player.start_track(len(self.player.queue) - 1)
         self.update_gui()
 
+    def on_save_button_toggled(self, button):
+        track_id = self.player.queue[self.player.index]['id']
+        if track_id is None:
+            button.set_active(False)
+        elif button.get_active():
+            track_id, track_data, album_id, album_data, artist_id, artist_data = self.temporary_library.get_track(
+                track_id)
+            self.permanent_library.add_track(
+                artist_id, artist_data, album_id, album_data, track_id, track_data)
+        else:
+            self.permanent_library.remove_track(track_id)
+        self.permanent_library.save()
+
 
 if __name__ == "__main__":
     App()

+ 23 - 12
libricia-music.glade

@@ -107,6 +107,20 @@ audio-volume-medium-symbolic</property>
                   </packing>
                 </child>
                 <child>
+                  <object class="GtkToggleToolButton" id="savebutton">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="label" translatable="yes">save</property>
+                    <property name="use_underline">True</property>
+                    <property name="stock_id">gtk-yes</property>
+                    <signal name="toggled" handler="on_save_button_toggled" swapped="no"/>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="homogeneous">True</property>
+                  </packing>
+                </child>
+                <child>
                   <object class="GtkToolButton">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
@@ -221,13 +235,11 @@ audio-volume-medium-symbolic</property>
                 </child>
                 <child>
                   <object class="GtkTreeViewColumn">
-                    <property name="resizable">True</property>
-                    <property name="sizing">autosize</property>
-                    <property name="title" translatable="yes">Title</property>
+                    <property name="title" translatable="yes">Track</property>
                     <child>
                       <object class="GtkCellRendererText"/>
                       <attributes>
-                        <attribute name="text">3</attribute>
+                        <attribute name="text">2</attribute>
                       </attributes>
                     </child>
                   </object>
@@ -235,12 +247,11 @@ audio-volume-medium-symbolic</property>
                 <child>
                   <object class="GtkTreeViewColumn">
                     <property name="resizable">True</property>
-                    <property name="sizing">autosize</property>
-                    <property name="title" translatable="yes">Artist</property>
+                    <property name="title" translatable="yes">Title</property>
                     <child>
                       <object class="GtkCellRendererText"/>
                       <attributes>
-                        <attribute name="text">4</attribute>
+                        <attribute name="text">3</attribute>
                       </attributes>
                     </child>
                   </object>
@@ -248,23 +259,23 @@ audio-volume-medium-symbolic</property>
                 <child>
                   <object class="GtkTreeViewColumn">
                     <property name="resizable">True</property>
-                    <property name="sizing">autosize</property>
-                    <property name="title" translatable="yes">Album</property>
+                    <property name="title" translatable="yes">Artist</property>
                     <child>
                       <object class="GtkCellRendererText"/>
                       <attributes>
-                        <attribute name="text">5</attribute>
+                        <attribute name="text">4</attribute>
                       </attributes>
                     </child>
                   </object>
                 </child>
                 <child>
                   <object class="GtkTreeViewColumn">
-                    <property name="title" translatable="yes">Track</property>
+                    <property name="resizable">True</property>
+                    <property name="title" translatable="yes">Album</property>
                     <child>
                       <object class="GtkCellRendererText"/>
                       <attributes>
-                        <attribute name="text">2</attribute>
+                        <attribute name="text">5</attribute>
                       </attributes>
                     </child>
                   </object>

+ 5 - 1
playback.py

@@ -99,11 +99,15 @@ class Player:
             else:
                 self.start_track(self.index - 1)
 
+    def get_now_playing(self):
+        if self.is_ready():
+            return self.queue[self.index]['id']
+
     def update_gui(self, liststore, playback_bar, playback_bar_is_pressed, playback_timestamp, playpause_button):
         if self.is_ready():
 
             # update playback marker
-            now_playing = self.queue[self.index]['id']
+            now_playing = self.get_now_playing()
 
             def update_icon(model, path, itr, data):
                 if model.get_value(itr, 0) == data:

+ 23 - 0
settings.py

@@ -0,0 +1,23 @@
+#    Copyright (C) 2019 Mason Hock <mason@masonhock.com>
+#
+#    This program is free software; you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation; either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program; if not, write to the Free Software
+#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+
+import os
+
+
+config_dir = os.path.expanduser("~") + '/.config/libricia-music'
+if not os.path.isdir(config_dir):
+    os.makedirs(config_dir)