1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
|
/**
* GJay, copyright (c) 2002 Chuck Groom
*
* 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 1, 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include "gjay.h"
#include "ui.h"
#include "analysis.h"
#include "playlist.h"
static song * current;
static song * first;
static void remove_repeats ( song * s, GList * list);
/* How much does brightness factor into matching two songs? */
#define BRIGHTNESS_FACTOR .8
#define PATH_DIST_FACTOR 0.5
/* Limit size of working set to 1500 songs */
#define MAX_WORKING_SET 1500
/**
* Generate a playlist (list of song *) no longer than the specified
* time, in minutes
*/
GList * generate_playlist ( guint minutes ) {
GList * working, * final, * rand_list, * list;
gint i, list_time, l, r, max_force_index, len;
gdouble max_force, s_force;
song * s;
time_t t;
list_time = 0;
if (verbosity)
t = time(NULL);
if (!g_list_length(songs))
return NULL;
/* Create a working set, a copy of the songs list */
if (prefs.use_selected_songs)
working = g_list_copy(selected_songs);
else
working = g_list_copy(songs);
final = NULL;
for (list = g_list_first(working); list; ) {
current = SONG(list);
/* OK, hold your breath. We exclude songs which:
* 1. Are not in the current file tree
* 2. Are no longer present
* 3. Are below the rating cutoff, if applied by the user
* 4. Are not in the currently selected directory, if the user
* specified that s/he wanted to limit the playlist to the
* current dir. */
if ((!current->in_tree) ||
(!current->access_ok) ||
(prefs.use_ratings && prefs.rating_cutoff &&
(current->rating < prefs.rating)) ||
(prefs.use_selected_dir &&
selected_files &&
(strncmp((char *) selected_files->data, current->path,
strlen((char *) selected_files->data)) != 0))) {
if (g_list_next(list))
list = g_list_next(list);
else
list = g_list_previous(list);
working = g_list_remove(working, current);
} else {
list = g_list_next(list);
}
}
if (!working) {
display_message("No songs to create playlist from");
return NULL;
}
/* Pick the first song */
first = NULL;
if (prefs.start_selected) {
for (list = g_list_first(working); list && !first;
list = g_list_next(list)) {
if (strncmp((char *) selected_files->data, SONG(list)->path,
strlen((char *) selected_files->data)) == 0)
first = SONG(list);
}
if (!first) {
gchar * latin1;
latin1 = strdup_to_latin1((char *) selected_files->data);
fprintf(stderr, "File '%s' not found in data file;\n"\
"perhaps it has not been analyzed. Using random starting song.\n",
latin1);
g_free(latin1);
}
}
if (prefs.start_color) {
song temp_song;
bzero(&temp_song, sizeof(song));
temp_song.no_data = TRUE;
temp_song.no_rating = TRUE;
temp_song.color = prefs.color;
for (max_force = -1000, list = g_list_first(working);
list;
list = g_list_next(list)) {
s = SONG(list);
s_force = song_force(&temp_song, s);
if (s_force > max_force) {
max_force = s_force;
first = s;
}
}
}
if (!first) {
/* Pick random starting song */
i = rand() % g_list_length(working);
first = SONG(g_list_nth(working, i));
}
final = g_list_append(final, first);
working = g_list_remove (working, first);
current = first;
/* If the song had any duplicates (symlinks) in the working
* list, remove them from the working list, too */
remove_repeats(current, working);
/* Regretably, we must winnow the working set to something reasonable.
If there were 10,000 songs, this would take ~20 seconds on a fast
machine. */
for (len = g_list_length(working); len > MAX_WORKING_SET; len--) {
s = SONG(g_list_nth(working, rand() % len));
working = g_list_remove(working, s);
}
/* Pick the rest of the songs */
while (working && (list_time < minutes * 60)) {
/* Divide working list into { random set, leftover }. Then
* pick the best song in random set. */
rand_list = NULL;
max_force = -10000;
max_force_index = -1;
l = g_list_length(working);
r = MAX(1, (l * prefs.variance) / MAX_CRITERIA );
/* Reduce copy of working to size of random list */
len = g_list_length(working);
while(r--) {
s = SONG(g_list_nth(working, rand() % len));
working = g_list_remove(working, s);
rand_list = g_list_append(rand_list, s);
len--;
/* Find the closest song */
if (prefs.wander)
s_force = song_force(s, current);
else
s_force = song_force(s, first);
if (s_force > max_force) {
max_force = s_force;
max_force_index = g_list_length(rand_list) - 1;
}
}
current = SONG(g_list_nth(rand_list, max_force_index));
list_time += current->length;
final = g_list_append(final, current);
rand_list = g_list_remove(rand_list, current);
working = g_list_concat(working, rand_list);
/* If the song had any duplicates (symlinks) in the working
* list, remove them from the working list, too */
remove_repeats(current, working);
}
if (final && (list_time > minutes * 60)) {
list_time -= SONG(g_list_last(final))->length;
final = g_list_remove(final, SONG(g_list_last(final)));
}
g_list_free(working);
if (verbosity)
printf("It took %d seconds to generate playlist\n",
(int) (time(NULL) - t));
return final;
}
void save_playlist ( GList * list, gchar * fname ) {
FILE * f;
f = fopen(fname, "w");
if (!f) {
display_message("Sorry, cannot write playlist there.");
return;
}
write_playlist(list, f, TRUE);
fclose(f);
}
void write_playlist ( GList * list, FILE * f, gboolean m3u_format) {
song * s;
gchar * l1_artist, * l1_title, * l1_path, * l1_fname;
if (m3u_format)
fprintf(f, "#EXTM3U\n");
for (; list; list = g_list_next(list)) {
s = SONG(list);
if (m3u_format) {
if (s->title) {
l1_artist = strdup_to_latin1(s->artist);
l1_title = strdup_to_latin1(s->title);
fprintf(f, "#EXTINF:%d,%s - %s\n",
s->length,
l1_artist,
l1_title);
g_free(l1_artist);
g_free(l1_title);
} else {
l1_fname = strdup_to_latin1(s->fname);
fprintf(f, "#EXTINF:%d,%s\n",
s->length,
l1_fname);
g_free(l1_fname);
}
}
l1_path = strdup_to_latin1(s->path);
fprintf(f, "%s\n", l1_path);
g_free(l1_path);
}
}
static void remove_repeats ( song * s, GList * list) {
song * repeat;
for (repeat = s->repeat_prev; repeat;
repeat = repeat->repeat_prev) {
list = g_list_remove(list, repeat);
}
for (repeat = s->repeat_next; repeat;
repeat = repeat->repeat_next) {
list = g_list_remove(list, repeat);
}
}
|