mirror of
https://github.com/bspeice/Melodia
synced 2024-12-04 13:58:18 -05:00
Upload the initial documentation for the archiver application
This commit is contained in:
parent
a515a4b8d3
commit
441f57bb2b
56
archiver/listfield.py
Normal file
56
archiver/listfield.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
'''
|
||||||
|
Testing documentation
|
||||||
|
'''
|
||||||
|
from django.db import models
|
||||||
|
import re, itertools
|
||||||
|
|
||||||
|
class IntegerListField(models.TextField):
|
||||||
|
"""
|
||||||
|
Store a list of integers in a database string.
|
||||||
|
Format is:
|
||||||
|
[<int_1>, <int_2>, <int_3>, ... , <int_n>]
|
||||||
|
"""
|
||||||
|
|
||||||
|
description = "Field type for storing lists of integers."
|
||||||
|
|
||||||
|
__metaclass__ = models.SubfieldBase
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(IntegerListField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
#Convert database to python
|
||||||
|
def to_python(self, value):
|
||||||
|
if isinstance(value, list):
|
||||||
|
return value
|
||||||
|
|
||||||
|
#Process a database string
|
||||||
|
|
||||||
|
#Validation first
|
||||||
|
if len(value) <= 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if value[0] != '[' or value[-1] != ']':
|
||||||
|
raise ValidationError("Invalid input to parse a list of integers!")
|
||||||
|
|
||||||
|
#Note that any non-digit string is a valid separator
|
||||||
|
_csv_regex = "[0-9]"
|
||||||
|
csv_regex = re.compile(_csv_regex)
|
||||||
|
|
||||||
|
#Synonymous to:
|
||||||
|
#string_list = filter(None, csv_regex.findall(value))
|
||||||
|
string_list = itertools.ifilter(None, csv_regex.findall(value))
|
||||||
|
value_list = [int(i) for i in string_list]
|
||||||
|
|
||||||
|
return value_list
|
||||||
|
|
||||||
|
#Convert python to database
|
||||||
|
def get_prep_value(self, value):
|
||||||
|
if not isinstance(value, list):
|
||||||
|
raise ValidationError("Invalid list given to put in database!")
|
||||||
|
|
||||||
|
separator_string = ", "
|
||||||
|
|
||||||
|
list_elements = separator_string.join(map(str, value))
|
||||||
|
|
||||||
|
return "[" + list_elements + "]"
|
@ -1,10 +1,3 @@
|
|||||||
'''
|
|
||||||
.. currentmodule:: archiver.models
|
|
||||||
|
|
||||||
I'm trying to link to :class:`~archiver.models.archive.Archive`!
|
|
||||||
|
|
||||||
'''
|
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
from archive import Archive
|
from archive import Archive
|
||||||
from song import Song
|
from song import Song
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""
|
"""
|
||||||
.. module:: archiver.models.archive
|
|
||||||
|
|
||||||
This is the Archive model for the backend of Melodia. It's functionality is to
|
This is the Archive model for the backend of Melodia. It's functionality is to
|
||||||
provide a grouping of songs based on where they are located in the filesystem.
|
provide a grouping of songs based on where they are located in the filesystem.
|
||||||
It controls the high-level functionality of managing multiple archives
|
It controls the high-level functionality of managing multiple archives
|
||||||
|
@ -113,5 +113,8 @@ class Feed(models.Model):
|
|||||||
:param dry_run: Calculate what would have been downloaded or deleted, but do not actually do either.
|
:param dry_run: Calculate what would have been downloaded or deleted, but do not actually do either.
|
||||||
:param forbid_delete: Run, and only download new episodes. Ignores the :data:`max_episodes` field for this podcast.
|
:param forbid_delete: Run, and only download new episodes. Ignores the :data:`max_episodes` field for this podcast.
|
||||||
|
|
||||||
|
.. todo::
|
||||||
|
Actually write this method...
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
@ -1,24 +1,17 @@
|
|||||||
"""
|
"""
|
||||||
.. module:: archiver.models
|
The Playlist model is simply that - it's a playlist of songs. However, we do
|
||||||
|
have to guarantee the song order, in addition to re-ordering the playlist.
|
||||||
Playlist model
|
As such, a :class:`models.ManyToManyField` isn't sufficient. We use a custom
|
||||||
Each playlist is a high-level ordering of songs. There really isn't much to a playlist - just its name, and the songs inside it.
|
database field to store a list of integers - the :class:`IntegerListField`.
|
||||||
However, we need to have a way to guarantee song order, in addition to re-ordering. A ManyToMany field can't do this.
|
This way we can guarantee song order, re-order the playlist, have songs
|
||||||
As such, a custom IntegerListField is implemented - it takes a python list of ints, converts it to a text field in the DB,
|
appear multiple times, etc.
|
||||||
and then back to a python list. This way, we can guarantee order, and have a song appear multiple times.
|
|
||||||
The IntegerListField itself uses the ID of each song as the int in a list. For example, a list of:
|
|
||||||
|
|
||||||
[1, 3, 5, 17]
|
|
||||||
|
|
||||||
Means that the playlist is made up of four songs. The order of the playlist is the song with index 1, 3, 5, and 17.
|
|
||||||
Additionally, the ManyToMany field is included to make sure we don't use the global Songs manager - it just seems hackish.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
||||||
from song import Song
|
from song import Song
|
||||||
from listfield import IntegerListField
|
from archiver.listfield import IntegerListField
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
@ -28,83 +21,74 @@ class Playlist(models.Model):
|
|||||||
app_label = 'archiver'
|
app_label = 'archiver'
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The Playlist class defines the playlist model and its operations.
|
.. data:: name
|
||||||
Currently supported are add, move, and remove operations, as well as exporting to
|
|
||||||
multiple formats.
|
String with the human-readable name for this playlist.
|
||||||
|
|
||||||
|
.. data:: song_list
|
||||||
|
|
||||||
|
List made up of Python integers. Each integer is assumed
|
||||||
|
to be a primary key to the :data:`Song.id` field for a song.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = models.CharField(max_length = 255)
|
name = models.CharField(max_length = 255)
|
||||||
song_list = IntegerListField()
|
song_list = IntegerListField()
|
||||||
|
|
||||||
#This is a bit of a backup field, since technically the song PK's are in
|
|
||||||
#the song_order field. However, it might be useful to just get a reference
|
|
||||||
#to the songs in this playlist. Additionally, it's kind of hackish to reference
|
|
||||||
#the global Song manager, rather than something that is specific for this playlist.
|
|
||||||
songs = models.ManyToManyField(Song)
|
|
||||||
|
|
||||||
def _populate_songs(self):
|
|
||||||
"""
|
|
||||||
Make sure that the 'songs' relation is up-to-date.
|
|
||||||
"""
|
|
||||||
#This operation works by getting the ID's for all songs currently in the playlist,
|
|
||||||
#calculates what we need to add, what we need to remove, and then does it.
|
|
||||||
#As much work as is possible is done on the python side to avoid the DB at all costs.
|
|
||||||
current_song_ids = [song.id for song in self.songs.all()]
|
|
||||||
current_song_ids_set = set(current_song_ids)
|
|
||||||
|
|
||||||
new_song_ids_set = set(self.song_list)
|
|
||||||
|
|
||||||
remove_set = current_song_ids_set.difference(new_song_ids_set)
|
|
||||||
add_set = new_song_ids_set.difference(current_song_ids_set)
|
|
||||||
|
|
||||||
for song_id in remove_set:
|
|
||||||
song = self.songs.get(id = song_id)
|
|
||||||
song.remove()
|
|
||||||
|
|
||||||
for song_id in add_set:
|
|
||||||
song = Song.objects.get(id = song_id) #Using the global Songs manager is unavoidable for this one
|
|
||||||
self.songs.add(song)
|
|
||||||
|
|
||||||
def insert(self, position, new_song):
|
def insert(self, position, new_song):
|
||||||
"""
|
"""
|
||||||
Insert a new song into the playlist at a specific position.
|
Insert a new song into the playlist at a specific position.
|
||||||
|
|
||||||
|
:param position: **Index** for the position this new song should be inserted at.
|
||||||
|
:param new_song: Reference to a :class:`Song` instance that will be inserted.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not isinstance(new_song, Song):
|
if not isinstance(new_song, Song):
|
||||||
#Not given a song reference, raise an error
|
#Not given a song reference, raise an error
|
||||||
raise ValidationError("Not given a song reference to insert.")
|
raise ValidationError("Not given a song reference to insert.")
|
||||||
|
|
||||||
self.songs.add(new_song)
|
|
||||||
|
|
||||||
self.song_list.insert(position, new_song.id)
|
self.song_list.insert(position, new_song.id)
|
||||||
|
|
||||||
self._populate_songs()
|
|
||||||
|
|
||||||
def append(self, new_song):
|
def append(self, new_song):
|
||||||
"""
|
"""
|
||||||
Add a new song to the end of the playlist.
|
Add a new song to the end of the playlist.
|
||||||
|
|
||||||
|
:param new_song: Reference to a :class:`Song` instance to be appended.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not isinstance(new_song, Song):
|
if not isinstance(new_song, Song):
|
||||||
#Not given a song reference, raise an error
|
#Not given a song reference, raise an error
|
||||||
raise ValidationError("Not given a song reference to insert.")
|
raise ValidationError("Not given a song reference to insert.")
|
||||||
|
|
||||||
self.songs.add(new_song)
|
|
||||||
|
|
||||||
self.song_list.append(new_song.id)
|
self.song_list.append(new_song.id)
|
||||||
|
|
||||||
self._populate_songs()
|
|
||||||
|
|
||||||
def move(self, original_position, new_position):
|
def move(self, original_position, new_position):
|
||||||
"""
|
"""
|
||||||
Move a song from one position to another
|
Move a song from one position to another
|
||||||
|
|
||||||
|
:param original_position: The index of the song we want to move
|
||||||
|
:param new_position: The index of where the song should be. See note below.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
When moving songs, it's a bit weird since the index we're actually
|
||||||
|
moving to may change. Consider the scenario --
|
||||||
|
|
||||||
|
* Function called with indexes 4 and 6
|
||||||
|
|
||||||
|
* Song removed from index 4
|
||||||
|
|
||||||
|
* The song that was in index 6 is now at index 5
|
||||||
|
|
||||||
|
* Song inserted at index 6 in new list - one further than originally intended.
|
||||||
|
|
||||||
|
As such, the behavior is that the song at index ``original_position`` is placed
|
||||||
|
above the song at ``new_position`` when this function is called.
|
||||||
"""
|
"""
|
||||||
if original_position == new_position:
|
if original_position == new_position:
|
||||||
return
|
return
|
||||||
|
|
||||||
song_id = self.song_list[original_position]
|
song_id = self.song_list[original_position]
|
||||||
|
|
||||||
#This is actually a bit more complicated than it first appears, since the index we're moving to may actually change
|
|
||||||
#when we remove an item.
|
|
||||||
if new_position < original_position:
|
if new_position < original_position:
|
||||||
del self.song_list[original_position]
|
del self.song_list[original_position]
|
||||||
self.song_list.insert(new_position, song_id)
|
self.song_list.insert(new_position, song_id)
|
||||||
@ -113,25 +97,29 @@ class Playlist(models.Model):
|
|||||||
del self.song_list[original_position]
|
del self.song_list[original_position]
|
||||||
self.song_list.insert(new_position - 1, song_id) #Account for the list indices shifting down.
|
self.song_list.insert(new_position - 1, song_id) #Account for the list indices shifting down.
|
||||||
|
|
||||||
self._populate_songs()
|
|
||||||
|
|
||||||
def remove(self, position):
|
def remove(self, position):
|
||||||
|
"""
|
||||||
|
Remove a song from this playlist.
|
||||||
|
|
||||||
|
:param position: Index of the song to be removed
|
||||||
|
"""
|
||||||
if position > len(self.song_list):
|
if position > len(self.song_list):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
del self.song_list[position]
|
del self.song_list[position]
|
||||||
|
|
||||||
self._populate_songs()
|
|
||||||
|
|
||||||
def export(self, playlist_type = "m3u"):
|
def export(self, playlist_type = "m3u"):
|
||||||
"""
|
"""
|
||||||
Export this internal playlist to a file format.
|
Export this internal playlist to a file format.
|
||||||
Supported formats:
|
Supported formats:
|
||||||
-pls
|
|
||||||
-m3u
|
* pls
|
||||||
Return value is a string containing the playlist -
|
* m3u
|
||||||
you can write it to file as you deem necessary.
|
|
||||||
|
:param playlist_type: String containing the file type to export to
|
||||||
|
:rtype: String containing the file content for this playlist.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if playlist_type == "pls":
|
if playlist_type == "pls":
|
||||||
#Playlist header
|
#Playlist header
|
||||||
playlist_string = "[playlist]"
|
playlist_string = "[playlist]"
|
||||||
@ -162,13 +150,26 @@ class Playlist(models.Model):
|
|||||||
|
|
||||||
return playlist_string
|
return playlist_string
|
||||||
|
|
||||||
def _import(self, playlist_string = None):
|
def playlist_import(self, playlist_string = None):
|
||||||
"""
|
"""
|
||||||
Import and convert a playlist into native DB format.
|
Import and convert a playlist into native DB format.
|
||||||
This function will return true if the playlist format was recognized, false otherwise.
|
|
||||||
It will return true even if there are errors processing individual songs in the playlist.
|
:param playlist_string: A string with the file content we're trying to import.
|
||||||
As a side note - the _import() name is used since python doesn't let
|
|
||||||
you name a function import().
|
:rtype: Returns true of the playlist format was recognized. See notes on processing below.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
The semantics on returning are nitpicky. This function will return ``False`` if the
|
||||||
|
playlist format was not recognized. If there are errors in processing, this
|
||||||
|
function will still return ``True``.
|
||||||
|
|
||||||
|
For example, if you try to import a song which does not exist in an :class:`Archive`,
|
||||||
|
it will fail that song silently.
|
||||||
|
|
||||||
|
.. todo::
|
||||||
|
|
||||||
|
Actually write the import code.
|
||||||
"""
|
"""
|
||||||
#TODO: Code playlist importing
|
#TODO: Code playlist importing
|
||||||
self.song_list = []
|
self.song_list = []
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
"""
|
||||||
|
The :class:`Song` model is by far the most complicated and involved model.
|
||||||
|
Each instance is a single music file. This model is used to store metadata
|
||||||
|
about the song.
|
||||||
|
"""
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from Melodia import melodia_settings
|
from Melodia import melodia_settings
|
||||||
|
|
||||||
@ -5,12 +11,6 @@ from archive import Archive
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import os.path
|
import os.path
|
||||||
"""
|
|
||||||
The Song model
|
|
||||||
Each instance of a Song represents a single music file.
|
|
||||||
This database model is used for storing the metadata information about a song,
|
|
||||||
and helps in doing sorting etc.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_default_string = "_UNAVAILABLE_"
|
_default_string = "_UNAVAILABLE_"
|
||||||
_default_date = datetime.datetime.now
|
_default_date = datetime.datetime.now
|
||||||
@ -32,18 +32,119 @@ _default_rating_choices = (
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Song (models.Model):
|
class Song (models.Model):
|
||||||
class Meta:
|
|
||||||
app_label = 'archiver'
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This class defines the fields and functions related to controlling
|
.. data:: title
|
||||||
individual music files.
|
|
||||||
Note that the Playlist model depends on this model's PK being an int.
|
Title tag of this song
|
||||||
|
|
||||||
|
.. data:: artist
|
||||||
|
|
||||||
|
Artist tag of this song.
|
||||||
|
|
||||||
|
.. data:: album_artist
|
||||||
|
|
||||||
|
Album artist tag of this song. Can be used to group albums where
|
||||||
|
individual songs were made by different people.
|
||||||
|
|
||||||
|
.. data:: album
|
||||||
|
|
||||||
|
Album tag of this song.
|
||||||
|
|
||||||
|
.. data:: year
|
||||||
|
|
||||||
|
Integer representing the year this song was made.
|
||||||
|
|
||||||
|
.. data:: genre
|
||||||
|
|
||||||
|
Genre tag of this song. This is a general :class:`models.CharField`
|
||||||
|
field, and is not limited to a specific set of genres.
|
||||||
|
|
||||||
|
.. data:: bpm
|
||||||
|
|
||||||
|
Beats per minute of this song (integer).
|
||||||
|
|
||||||
|
.. data:: disc_number
|
||||||
|
|
||||||
|
Disc number this song came from
|
||||||
|
|
||||||
|
.. data:: disc_total
|
||||||
|
|
||||||
|
Total number of discs in the album this song is from
|
||||||
|
|
||||||
|
.. data:: track_number
|
||||||
|
|
||||||
|
Track number in the album this song is from
|
||||||
|
|
||||||
|
.. data:: track_total
|
||||||
|
|
||||||
|
Total number of tracks in the album this song is from
|
||||||
|
|
||||||
|
.. data:: comment
|
||||||
|
|
||||||
|
Comment tag of this song
|
||||||
|
|
||||||
|
.. data:: bit_rate
|
||||||
|
|
||||||
|
Integer representing the bit rate of this song
|
||||||
|
|
||||||
|
.. data:: duration
|
||||||
|
|
||||||
|
Duration (in seconds, floating-point value) of this song
|
||||||
|
|
||||||
|
.. data:: add_date
|
||||||
|
|
||||||
|
Date (not time) this song was added to the DB. Should **not** be
|
||||||
|
modified outside of this class' methods.
|
||||||
|
|
||||||
|
.. data:: url
|
||||||
|
|
||||||
|
URL for where this file is located on disk.
|
||||||
|
|
||||||
|
.. data:: file_hash
|
||||||
|
|
||||||
|
The hash string for this file - used to quickly check if the file has
|
||||||
|
been modified.
|
||||||
|
|
||||||
|
.. data:: file_size
|
||||||
|
|
||||||
|
Size of the file in bytes.
|
||||||
|
|
||||||
|
.. data:: play_count
|
||||||
|
|
||||||
|
How many times this file has been played through (defined as greater
|
||||||
|
than 50% of the song heard before skipping)
|
||||||
|
|
||||||
|
.. data:: skip_count
|
||||||
|
|
||||||
|
How many times this file has been skipped (defined as less than 50% of
|
||||||
|
the song heard before skipping)
|
||||||
|
|
||||||
|
.. data:: rating
|
||||||
|
|
||||||
|
Rating for this song. Ratings are as follows in order of increasing favoredness
|
||||||
|
on a 1--5 scale --
|
||||||
|
|
||||||
|
========= ======== ================
|
||||||
|
Rating: Value: Class field:
|
||||||
|
========= ======== ================
|
||||||
|
Default 0 RATING_DEFAULT
|
||||||
|
Bad 1 RATING_BAD
|
||||||
|
OK 2 RATING_OK
|
||||||
|
Decent 3 RATING_DECENT
|
||||||
|
Good 4 RATING_GOOD
|
||||||
|
Excellent 5 RATING_EXCELLENT
|
||||||
|
========= ======== ================
|
||||||
|
|
||||||
|
.. todo::
|
||||||
|
|
||||||
|
Change defaults to allow for ``None`` instead
|
||||||
|
Change private functions to public as need be
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#Standard song metadata
|
#Standard song metadata
|
||||||
title = models.CharField(max_length = 64, default = _default_string)
|
title = models.CharField(max_length = 64, default = _default_string)
|
||||||
artist = models.CharField(max_length = 64, default = _default_string)
|
artist = models.CharField(max_length = 64, default = _default_string)
|
||||||
|
album_artist = models.CharField(max_length = 64, default = _default_string)
|
||||||
album = models.CharField(max_length = 64, default = _default_string)
|
album = models.CharField(max_length = 64, default = _default_string)
|
||||||
year = models.IntegerField(default = _default_string)
|
year = models.IntegerField(default = _default_string)
|
||||||
genre = models.CharField(max_length = 64, default = _default_string)
|
genre = models.CharField(max_length = 64, default = _default_string)
|
||||||
@ -58,7 +159,6 @@ class Song (models.Model):
|
|||||||
bit_rate = models.IntegerField(default = _default_int)
|
bit_rate = models.IntegerField(default = _default_int)
|
||||||
duration = models.FloatField(default = _default_int)
|
duration = models.FloatField(default = _default_int)
|
||||||
add_date = models.DateField(default = _default_date)
|
add_date = models.DateField(default = _default_date)
|
||||||
echonest_song_id = models.CharField(max_length = 64, default = _default_string)
|
|
||||||
url = models.CharField(max_length = 255)
|
url = models.CharField(max_length = 255)
|
||||||
file_hash = melodia_settings.HASH_RESULT_DB_TYPE
|
file_hash = melodia_settings.HASH_RESULT_DB_TYPE
|
||||||
file_size = models.IntegerField(default = _default_int)
|
file_size = models.IntegerField(default = _default_int)
|
||||||
@ -79,6 +179,9 @@ class Song (models.Model):
|
|||||||
RATING_GOOD = _default_rating_good
|
RATING_GOOD = _default_rating_good
|
||||||
RATING_EXCELLENT = _default_rating_excellent
|
RATING_EXCELLENT = _default_rating_excellent
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
app_label = 'archiver'
|
||||||
|
|
||||||
def _get_full_url(self):
|
def _get_full_url(self):
|
||||||
"Combine this song's URL with the URL of its parent"
|
"Combine this song's URL with the URL of its parent"
|
||||||
return os.path.join(parent_archive.root_folder, self.url)
|
return os.path.join(parent_archive.root_folder, self.url)
|
||||||
@ -111,10 +214,6 @@ class Song (models.Model):
|
|||||||
self.file_hash = hash(file_handle.read())
|
self.file_hash = hash(file_handle.read())
|
||||||
self.file_size = os.stat(self._get_full_url).st_size
|
self.file_size = os.stat(self._get_full_url).st_size
|
||||||
|
|
||||||
def _grab_metadata_echonest(self):
|
|
||||||
"Populate this song's metadata using EchoNest"
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _grab_metadata_local(self):
|
def _grab_metadata_local(self):
|
||||||
"Populate this song's metadata using what is locally available"
|
"Populate this song's metadata using what is locally available"
|
||||||
#Use mutagen to get the song metadata
|
#Use mutagen to get the song metadata
|
||||||
@ -145,8 +244,10 @@ class Song (models.Model):
|
|||||||
#Couldn't grab the local data
|
#Couldn't grab the local data
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def populate_metadata(self, use_echonest = False):
|
def populate_metadata(self):
|
||||||
"Populate the metadata of this song (only if file hash has changed), and save the result."
|
"""
|
||||||
|
Populate the metadata of this song (only if file hash has changed), and save the result.
|
||||||
|
"""
|
||||||
if self._file_not_changed():
|
if self._file_not_changed():
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -157,6 +258,15 @@ class Song (models.Model):
|
|||||||
else:
|
else:
|
||||||
self._grab_metadata_local()
|
self._grab_metadata_local()
|
||||||
|
|
||||||
def convert(self, output_location, output_format, progress_func = lambda x, y: None):
|
def convert(self, output_location, output_format):
|
||||||
"Convert a song to a new format."
|
"""
|
||||||
|
Convert a song to a new format.
|
||||||
|
|
||||||
|
:param output_location: String URL of where the resulting file should be stored
|
||||||
|
:param output_format: Output format of the resulting file
|
||||||
|
|
||||||
|
.. todo::
|
||||||
|
|
||||||
|
Actually write the code to convert files, or abandon if necessary
|
||||||
|
"""
|
||||||
pass #Need to get pydub code in place
|
pass #Need to get pydub code in place
|
||||||
|
@ -26,7 +26,13 @@ sys.path.insert(0, os.path.abspath('../..')) # Django project root
|
|||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage', 'sphinx.ext.viewcode']
|
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', 'sphinx.ext.todo']
|
||||||
|
|
||||||
|
# Enable TODO support
|
||||||
|
todo_include_todos = True
|
||||||
|
|
||||||
|
# Document class members in source order
|
||||||
|
autodoc_member_order = 'bysource'
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
|
39
doc/docs/archiver.models.rst
Normal file
39
doc/docs/archiver.models.rst
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
models Package
|
||||||
|
==============
|
||||||
|
|
||||||
|
:mod:`models` Package
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
.. automodule:: archiver.models
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
:mod:`archive` Module
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
.. automodule:: archiver.models.archive
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
:mod:`feed` Module
|
||||||
|
------------------
|
||||||
|
|
||||||
|
.. automodule:: archiver.models.feed
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
:mod:`playlist` Module
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
.. automodule:: archiver.models.playlist
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
:mod:`song` Module
|
||||||
|
------------------
|
||||||
|
|
||||||
|
.. automodule:: archiver.models.song
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
@ -1,5 +1,42 @@
|
|||||||
====
|
archiver Package
|
||||||
Archive backend documentation
|
================
|
||||||
====
|
|
||||||
|
:mod:`archiver` Package
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
.. automodule:: archiver.__init__
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
:mod:`listfield` Module
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
.. automodule:: archiver.listfield
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
:mod:`tests` Module
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. automodule:: archiver.tests
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
:mod:`views` Module
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. automodule:: archiver.views
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
Subpackages
|
||||||
|
-----------
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
|
||||||
|
archiver.models
|
||||||
|
|
||||||
.. automodule:: archiver
|
|
||||||
|
7
doc/docs/modules.rst
Normal file
7
doc/docs/modules.rst
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
archiver
|
||||||
|
========
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 4
|
||||||
|
|
||||||
|
archiver
|
Loading…
Reference in New Issue
Block a user