summaryrefslogtreecommitdiff
path: root/src/parse_smart_playlist.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/parse_smart_playlist.py')
-rw-r--r--src/parse_smart_playlist.py636
1 files changed, 636 insertions, 0 deletions
diff --git a/src/parse_smart_playlist.py b/src/parse_smart_playlist.py
new file mode 100644
index 0000000..0bfde9d
--- /dev/null
+++ b/src/parse_smart_playlist.py
@@ -0,0 +1,636 @@
1# // http://banshee-itunes-import-plugin.googlecode.com/svn/trunk/
2# using System;
3# using System.Collections.Generic;
4# using System.Text;
5# using Banshee.SmartPlaylist;
6#
7# namespace Banshee.Plugins.iTunesImporter
8# {
9# internal struct SmartPlaylist
10# {
11# public string Query, Ignore, OrderBy, Name, Output;
12# public uint LimitNumber;
13# public byte LimitMethod;
14# }
15#
16# internal static partial class SmartPlaylistParser
17# {
18# private delegate bool KindEvalDel(Kind kind, string query);
19#
20# // INFO OFFSETS
21# //
22# // Offsets for bytes which...
23# const int MATCHBOOLOFFSET = 1; // determin whether logical matching is to be performed - Absolute offset
24# const int LIMITBOOLOFFSET = 2; // determin whether results are limited - Absolute offset
25# const int LIMITMETHODOFFSET = 3; // determin by what criteria the results are limited - Absolute offset
26# const int SELECTIONMETHODOFFSET = 7; // determin by what criteria limited playlists are populated - Absolute offset
27# const int LIMITINTOFFSET = 11; // determin the limited - Absolute offset
28# const int SELECTIONMETHODSIGNOFFSET = 13;// determin whether certain selection methods are "most" or "least" - Absolute offset
29#
30# // CRITERIA OFFSETS
31# //
32# // Offsets for bytes which...
33# const int LOGICTYPEOFFSET = 15; // determin whether all or any criteria must match - Absolute offset
34# const int FIELDOFFSET = 139; // determin what is being matched (Artist, Album, &c) - Absolute offset
35# const int LOGICSIGNOFFSET = 1; // determin whether the matching rule is positive or negative (e.g., is vs. is not) - Relative offset from FIELDOFFSET
36# const int LOGICRULEOFFSET = 4; // determin the kind of logic used (is, contains, begins, &c) - Relative offset from FIELDOFFSET
37# const int STRINGOFFSET = 54; // begin string data - Relative offset from FIELDOFFSET
38# const int INTAOFFSET = 60; // begin the first int - Relative offset from FIELDOFFSET
39# const int INTBOFFSET = 24; // begin the second int - Relative offset from INTAOFFSET
40# const int TIMEMULTIPLEOFFSET = 76;// begin the int with the multiple of time - Relative offset from FIELDOFFSET
41# const int TIMEVALUEOFFSET = 68; // begin the inverse int with the value of time - Relative offset from FIELDOFFSET
42#
43# const int INTLENGTH = 64; // The length on a int criteria starting at the first int
44# static DateTime STARTOFTIME = new DateTime(1904, 1, 1); // Dates are recorded as seconds since Jan 1, 1904
45#
46# static bool or, again;
47# static string conjunctionOutput, conjunctionQuery, output, query, ignore;
48# static int offset, logicSignOffset,logicRulesOffset, stringOffset, intAOffset, intBOffset,
49# timeMultipleOffset, timeValueOffset;
50# static byte[] info, criteria;
51#
52# static KindEvalDel KindEval;
53#
54# public static SmartPlaylist Parse(byte[] i, byte[] c)
55# {
56# info = i;
57# criteria = c;
58# SmartPlaylist result = new SmartPlaylist();
59# offset = FIELDOFFSET;
60# output = "";
61# query = "";
62# ignore = "";
63#
64# if(info[MATCHBOOLOFFSET] == 1) {
65# or = (criteria[LOGICTYPEOFFSET] == 1) ? true : false;
66# if(or) {
67# conjunctionQuery = " OR ";
68# conjunctionOutput = " or\n";
69# } else {
70# conjunctionQuery = " AND ";
71# conjunctionOutput = " and\n";
72# }
73# do {
74# again = false;
75# logicSignOffset = offset + LOGICSIGNOFFSET;
76# logicRulesOffset = offset + LOGICRULEOFFSET;
77# stringOffset = offset + STRINGOFFSET;
78# intAOffset = offset + INTAOFFSET;
79# intBOffset = intAOffset + INTBOFFSET;
80# timeMultipleOffset = offset + TIMEMULTIPLEOFFSET;
81# timeValueOffset = offset + TIMEVALUEOFFSET;
82#
83# if(Enum.IsDefined(typeof(StringFields), (int)criteria[offset])) {
84# ProcessStringField();
85# } else if(Enum.IsDefined(typeof(IntFields), (int)criteria[offset])) {
86# ProcessIntField();
87# } else if(Enum.IsDefined(typeof(DateFields), (int)criteria[offset])) {
88# ProcessDateField();
89# } else {
90# ignore += "Not processed";
91# }
92# }
93# while(again);
94# }
95# result.Output = output;
96# result.Query = query;
97# result.Ignore = ignore;
98# if(info[LIMITBOOLOFFSET] == 1) {
99# uint limit = BytesToUInt(info, LIMITINTOFFSET);
100# result.LimitNumber = (info[LIMITMETHODOFFSET] == (byte)LimitMethods.GB) ? limit * 1024 : limit;
101# if(output.Length > 0) {
102# output += "\n";
103# }
104# output += "Limited to " + limit.ToString() + " " +
105# Enum.GetName(typeof(LimitMethods), (int)info[LIMITMETHODOFFSET]) + " selected by ";
106# switch(info[LIMITMETHODOFFSET]) {
107# case (byte)LimitMethods.Items:
108# result.LimitMethod = 0;
109# break;
110# case (byte)LimitMethods.Minutes:
111# result.LimitMethod = 1;
112# break;
113# case (byte)LimitMethods.Hours:
114# result.LimitMethod = 2;
115# break;
116# case (byte)LimitMethods.MB:
117# result.LimitMethod = 3;
118# break;
119# case (byte)LimitMethods.GB:
120# goto case (byte)LimitMethods.MB;
121# }
122# switch(info[SELECTIONMETHODOFFSET]) {
123# case (byte)SelectionMethods.Random:
124# output += "random";
125# result.OrderBy = "RANDOM()";
126# break;
127# case (byte)SelectionMethods.HighestRating:
128# output += "highest rated";
129# result.OrderBy = "Rating DESC";
130# break;
131# case (byte)SelectionMethods.LowestRating:
132# output += "lowest rated";
133# result.OrderBy = "Rating ASC";
134# break;
135# case (byte)SelectionMethods.RecentlyPlayed:
136# output += (info[SELECTIONMETHODSIGNOFFSET] == 0)
137# ? "most recently played" : "least recently played";
138# result.OrderBy = (info[SELECTIONMETHODSIGNOFFSET] == 0)
139# ? "LastPlayedStamp DESC" : "LastPlayedStamp ASC";
140# break;
141# case (byte)SelectionMethods.OftenPlayed:
142# output += (info[SELECTIONMETHODSIGNOFFSET] == 0)
143# ? "most often played" : "least often played";
144# result.OrderBy = (info[SELECTIONMETHODSIGNOFFSET] == 0)
145# ? "NumberOfPlays DESC" : "NumberOfPlays ASC";
146# break;
147# case (byte)SelectionMethods.RecentlyAdded:
148# output += (info[SELECTIONMETHODSIGNOFFSET] == 0)
149# ? "most recently added" : "least recently added";
150# result.OrderBy = (info[SELECTIONMETHODSIGNOFFSET] == 0)
151# ? "DateAddedStamp DESC" : "DateAddedStamp ASC";
152# break;
153# default:
154# result.OrderBy = Enum.GetName(typeof(SelectionMethods), (int)info[SELECTIONMETHODOFFSET]);
155# break;
156# }
157# }
158# if(ignore.Length > 0) {
159# output += "\n\nIGNORING:\n" + ignore;
160# }
161#
162# if(query.Length > 0) {
163# output += "\n\nQUERY:\n" + query;
164# }
165# return result;
166# }
167#
168# private static void ProcessStringField()
169# {
170# bool end = false;
171# string workingOutput = Enum.GetName(typeof(StringFields), criteria[offset]);
172# string workingQuery = "(lower(" + Enum.GetName(typeof(StringFields), criteria[offset]) + ")";
173# switch(criteria[logicRulesOffset]) {
174# case (byte)LogicRule.Contains:
175# if((criteria[logicSignOffset] == (byte)LogicSign.StringPositive)) {
176# workingOutput += " contains ";
177# workingQuery += " LIKE '%";
178# } else {
179# workingOutput += " does not contain ";
180# workingQuery += " NOT LIKE '%";
181# }
182# if(criteria[offset] == (byte)StringFields.Kind) {
183# KindEval = delegate(Kind kind, string query) {
184# return (kind.Name.IndexOf(query) != -1);
185# };
186# }
187# end = true;
188# break;
189# case (byte)LogicRule.Is:
190# if((criteria[logicSignOffset] == (byte)LogicSign.StringPositive)) {
191# workingOutput += " is ";
192# workingQuery += " = '";
193# } else {
194# workingOutput += " is not ";
195# workingQuery += " != '";
196# }
197# if(criteria[offset] == (byte)StringFields.Kind) {
198# KindEval = delegate(Kind kind, string query) {
199# return (kind.Name == query);
200# };
201# }
202# break;
203# case (byte)LogicRule.Starts:
204# workingOutput += " starts with ";
205# workingQuery += " LIKE '";
206# if(criteria[offset] == (byte)StringFields.Kind) {
207# KindEval = delegate (Kind kind, string query) {
208# return (kind.Name.IndexOf(query) == 0);
209# };
210# }
211# end = true;
212# break;
213# case (byte)LogicRule.Ends:
214# workingOutput += " ends with ";
215# workingQuery += " LIKE '%";
216# if(criteria[offset] == (byte)StringFields.Kind) {
217# KindEval = delegate (Kind kind, string query) {
218# return (kind.Name.IndexOf(query) == (kind.Name.Length - query.Length));
219# };
220# }
221# break;
222# }
223# workingOutput += "\"";
224# byte[] character = new byte[1];
225# string content = "";
226# bool onByte = true;
227# for(int i = (stringOffset); i < criteria.Length; i++) {
228# // Off bytes are 0
229# if(onByte) {
230# // If the byte is 0 and it's not the last byte,
231# // we have another condition
232# if(criteria[i] == 0 && i != (criteria.Length - 1)) {
233# again = true;
234# FinishStringField(content, workingOutput, workingQuery, end);
235# offset = i + 2;
236# return;
237# }
238# character[0] = criteria[i];
239# content += Encoding.UTF8.GetString(character);
240# }
241# onByte = !onByte;
242# }
243# FinishStringField(content, workingOutput, workingQuery, end);
244# }
245#
246# private static void FinishStringField(string content, string workingOutput, string workingQuery, bool end)
247# {
248# workingOutput += content;
249# workingOutput += "\" ";
250# bool failed = false;
251# if(criteria[offset] == (byte)StringFields.Kind) {
252# workingQuery = "";
253# foreach(Kind kind in Kinds) {
254# if(KindEval(kind, content)) {
255# if(workingQuery.Length > 0) {
256# if((query.Length == 0 && !again) || or) {
257# workingQuery += " OR ";
258# } else {
259# failed = true;
260# break;
261# }
262# }
263# workingQuery += "(lower(Uri)";
264# workingQuery += ((criteria[logicSignOffset] == (byte)LogicSign.StringPositive))
265# ? " LIKE '%" + kind.Extension + "')" : " NOT LIKE '%" + kind.Extension + "%')";
266# }
267# }
268# } else {
269# workingQuery += content.ToLower();
270# workingQuery += (end) ? "%')" : "')";
271# }
272# if(Enum.IsDefined(typeof(IgnoreStringFields),
273# (int)criteria[offset]) || failed) {
274# if(ignore.Length > 0) {
275# ignore += conjunctionOutput;
276# }
277# ignore += workingOutput;
278# } else {
279# if(output.Length > 0) {
280# output += conjunctionOutput;
281# }
282# if(query.Length > 0) {
283# query += conjunctionQuery;
284# }
285# output += workingOutput;
286# query += workingQuery;
287# }
288# }
289#
290# private static void ProcessIntField()
291# {
292# string workingOutput = Enum.GetName(typeof(IntFields), criteria[offset]);
293# string workingQuery = "(" + Enum.GetName(typeof(IntFields), criteria[offset]);
294#
295# switch(criteria[logicRulesOffset]) {
296# case (byte)LogicRule.Is:
297# if(criteria[logicSignOffset] == (byte)LogicSign.IntPositive) {
298# workingOutput += " is ";
299# workingQuery += " = ";
300# } else {
301# workingOutput += " is not ";
302# workingQuery += " != ";
303# }
304# goto case 255;
305# case (byte)LogicRule.Greater:
306# workingOutput += " is greater than ";
307# workingQuery += " > ";
308# goto case 255;
309# case (byte)LogicRule.Less:
310# workingOutput += " is less than ";
311# workingQuery += " > ";
312# goto case 255;
313# case 255:
314# uint number = (criteria[offset] == (byte)IntFields.Rating)
315# ? (BytesToUInt(criteria, intAOffset) / 20) : BytesToUInt(criteria, intAOffset);
316# workingOutput += number.ToString();
317# workingQuery += number.ToString();
318# break;
319# case (byte)LogicRule.Other:
320# if(criteria[logicSignOffset + 2] == 1) {
321# workingOutput += " is in the range of ";
322# workingQuery += " BETWEEN ";
323# uint num = (criteria[offset] == (byte)IntFields.Rating)
324# ? (BytesToUInt(criteria, intAOffset) / 20) : BytesToUInt(criteria, intAOffset);
325# workingOutput += num.ToString();
326# workingQuery += num.ToString();
327# workingOutput += " to ";
328# workingQuery += " AND ";
329# num = (criteria[offset] == (byte)IntFields.Rating)
330# ? ((BytesToUInt(criteria, intBOffset) - 19) / 20) : BytesToUInt(criteria, intBOffset);
331# workingOutput += num.ToString();
332# workingQuery += num.ToString();
333# }
334# break;
335# }
336# workingQuery += ")";
337# if(Enum.IsDefined(typeof(IgnoreIntFields),
338# (int)criteria[offset])) {
339# if(ignore.Length > 0) {
340# ignore += conjunctionOutput;
341# }
342# ignore += workingOutput;
343# } else {
344# if(output.Length > 0) {
345# output += conjunctionOutput;
346# }
347# if(query.Length > 0) {
348# query += conjunctionQuery;
349# }
350# output += workingOutput;
351# query += workingQuery;
352# }
353# offset = intAOffset + INTLENGTH;
354# if(criteria.Length > offset) {
355# again = true;
356# }
357# }
358#
359# private static void ProcessDateField()
360# {
361# bool isIgnore = false;
362# string workingOutput = Enum.GetName(typeof(DateFields), criteria[offset]);
363# string workingQuery = "((strftime(\"%s\", current_timestamp) - DateAddedStamp + 3600)";
364# switch(criteria[logicRulesOffset]) {
365# case (byte)LogicRule.Greater:
366# workingOutput += " is after ";
367# workingQuery += " > ";
368# goto case 255;
369# case (byte)LogicRule.Less:
370# workingOutput += " is before ";
371# workingQuery += " > ";
372# goto case 255;
373# case 255:
374# isIgnore = true;
375# DateTime time = BytesToDateTime(criteria, intAOffset);
376# workingOutput += time.ToString();
377# workingQuery += ((int)DateTime.Now.Subtract(time).TotalSeconds).ToString();
378# break;
379# case (byte)LogicRule.Other:
380# if(criteria[logicSignOffset + 2] == 1) {
381# isIgnore = true;
382# DateTime t2 = BytesToDateTime(criteria, intAOffset);
383# DateTime t1 = BytesToDateTime(criteria, intBOffset);
384# if(criteria[logicSignOffset] == (byte)LogicSign.IntPositive) {
385# workingOutput += " is in the range of ";
386# workingQuery += " BETWEEN " +
387# ((int)DateTime.Now.Subtract(t1).TotalSeconds).ToString() +
388# " AND " +
389# ((int)DateTime.Now.Subtract(t2).TotalSeconds).ToString();
390# } else {
391# workingOutput += " is not in the range of ";
392# }
393# workingOutput += t1.ToString();
394# workingOutput += " to ";
395# workingOutput += t2.ToString();
396# } else if(criteria[logicSignOffset + 2] == 2) {
397# if(criteria[logicSignOffset] == (byte)LogicSign.IntPositive) {
398# workingOutput += " is in the last ";
399# workingQuery += " < ";
400# } else {
401# workingOutput += " is not in the last ";
402# workingQuery += " > ";
403# }
404# uint t = InverseBytesToUInt(criteria, timeValueOffset);
405# uint multiple = BytesToUInt(criteria, timeMultipleOffset);
406# workingQuery += (t * multiple).ToString();
407# workingOutput += t.ToString() + " ";
408# switch(multiple) {
409# case 86400:
410# workingOutput += "days";
411# break;
412# case 604800:
413# workingOutput += "weeks";
414# break;
415# case 2628000:
416# workingOutput += "months";
417# break;
418# }
419# }
420# break;
421# }
422# workingQuery += ")";
423# if(isIgnore || Enum.IsDefined(typeof(IgnoreDateFields), (int)criteria[offset])) {
424# if(ignore.Length > 0) {
425# ignore += conjunctionOutput;
426# }
427# ignore += workingOutput;
428# } else {
429# if(output.Length > 0) {
430# output += conjunctionOutput;
431# }
432# output += workingOutput;
433# if(query.Length > 0) {
434# query += conjunctionQuery;
435# }
436# query += workingQuery;
437# }
438# offset = intAOffset + INTLENGTH;
439# if(criteria.Length > offset) {
440# again = true;
441# }
442# }
443#
444# /// <summary>
445# /// Converts 4 bytes to a uint
446# /// </summary>
447# /// <param name="byteArray">A byte array</param>
448# /// <param name="offset">Should be the byte of the uint with the 0th-power position</param>
449# /// <returns></returns>
450# private static uint BytesToUInt(byte[] byteArray, int offset)
451# {
452# uint output = 0;
453# for (byte i = 0; i <= 4; i++) {
454# output += (uint)(byteArray[offset - i] * Math.Pow(2, (8 * i)));
455# }
456# return output;
457# }
458#
459# private static uint InverseBytesToUInt(byte[] byteArray, int offset)
460# {
461# uint output = 0;
462# for (byte i = 0; i <= 4; i++) {
463# output += (uint)((255 - (uint)(byteArray[offset - i])) * Math.Pow(2, (8 * i)));
464# }
465# return ++output;
466# }
467#
468# private static DateTime BytesToDateTime (byte[] byteArray, int offset)
469# {
470# uint number = BytesToUInt(byteArray, offset);
471# return STARTOFTIME.AddSeconds(number);
472# }
473# }
474# }
475# namespace Banshee.Plugins.iTunesImporter
476# {
477# internal struct Kind
478# {
479# public string Name, Extension;
480# public Kind(string name, string extension)
481# {
482# Name = name;
483# Extension = extension;
484# }
485# }
486#
487# internal partial class SmartPlaylistParser
488# {
489# private static Kind[] Kinds = {
490# new Kind("Protected AAC audio file", ".m4p"),
491# new Kind("MPEG audio file", ".mp3"),
492# new Kind("AIFF audio file", ".aiff"),
493# new Kind("WAV audio file", ".wav"),
494# new Kind("QuickTime movie file", ".mov"),
495# new Kind("MPEG-4 video file", ".mp4"),
496# new Kind("AAC audio file", ".m4a")
497# };
498#
499# /// <summary>
500# /// The methods by which the number of songs in a playlist are limited
501# /// </summary>
502# private enum LimitMethods
503# {
504# Minutes = 0x01,
505# MB = 0x02,
506# Items = 0x03,
507# Hours = 0x04,
508# GB = 0x05,
509# }
510# /// <summary>
511# /// The methods by which songs are selected for inclusion in a limited playlist
512# /// </summary>
513# private enum SelectionMethods
514# {
515# Random = 0x02,
516# Title = 0x05,
517# AlbumTitle = 0x06,
518# Artist = 0x07,
519# Genre = 0x09,
520# HighestRating = 0x1c,
521# LowestRating = 0x01,
522# RecentlyPlayed = 0x1a,
523# OftenPlayed = 0x19,
524# RecentlyAdded = 0x15
525# }
526# /// <summary>
527# /// The matching criteria which take string data
528# /// </summary>
529# private enum StringFields
530# {
531# AlbumTitle = 0x03,
532# AlbumArtist = 0x47,
533# Artist = 0x04,
534# Category = 0x37,
535# Comments = 0x0e,
536# Composer = 0x12,
537# Description = 0x36,
538# Genre = 0x08,
539# Grouping = 0x27,
540# Kind = 0x09,
541# Title = 0x02,
542# Show = 0x3e
543# }
544# /// <summary>
545# /// The matching criteria which take integer data
546# /// </summary>
547# private enum IntFields
548# {
549# BPM = 0x23,
550# BitRate = 0x05,
551# Compilation = 0x1f,
552# DiskNumber = 0x18,
553# NumberOfPlays = 0x16,
554# Rating = 0x19,
555# Playlist = 0x28, // FIXME Move this?
556# Podcast = 0x39,
557# SampleRate = 0x06,
558# Season = 0x3f,
559# Size = 0x0c,
560# SkipCount = 0x44,
561# Duration = 0x0d,
562# TrackNumber = 0x0b,
563# VideoKind = 0x3c,
564# Year = 0x07
565# }
566# /// <summary>
567# /// The matching criteria which take date data
568# /// </summary>
569# private enum DateFields
570# {
571# DateAdded = 0x10,
572# DateModified = 0x0a,
573# LastPlayed = 0x17,
574# LastSkipped = 0x45
575# }
576# /// <summary>
577# /// The matching criteria which we do no handle
578# /// </summary>
579# private enum IgnoreStringFields
580# {
581# AlbumArtist = 0x47,
582# Category = 0x37,
583# Comments = 0x0e,
584# Composer = 0x12,
585# Description = 0x36,
586# Grouping = 0x27,
587# Show = 0x3e
588# }
589# /// <summary>
590# /// The matching criteria which we do no handle
591# /// </summary>
592# private enum IgnoreIntFields
593# {
594# BPM = 0x23,
595# BitRate = 0x05,
596# Compilation = 0x1f,
597# DiskNumber = 0x18,
598# Playlist = 0x28,
599# Podcast = 0x39,
600# SampleRate = 0x06,
601# Season = 0x3f,
602# Size = 0x0c,
603# SkipCount = 0x44,
604# TrackNumber = 0x0b,
605# VideoKind = 0x3c
606# }
607# private enum IgnoreDateFields
608# {
609# DateModified = 0x0a,
610# LastSkipped = 0x45
611# }
612# /// <summary>
613# /// The signs which apply to different kinds of logic (is vs. is not, contains vs. doesn't contain, etc.)
614# /// </summary>
615# private enum LogicSign
616# {
617# IntPositive = 0x00,
618# StringPositive = 0x01,
619# IntNegative = 0x02,
620# StringNegative = 0x03
621# }
622# /// <summary>
623# /// The logical rules
624# /// </summary>
625# private enum LogicRule
626# {
627# Other = 0x00,
628# Is = 0x01,
629# Contains = 0x02,
630# Starts = 0x04,
631# Ends = 0x08,
632# Greater = 0x10,
633# Less = 0x40
634# }
635# }
636# }