[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[gmediaserver-devel] [Patch] Add support for MPG files; speed up initial
From: |
Jan Ceuleers |
Subject: |
[gmediaserver-devel] [Patch] Add support for MPG files; speed up initial start-up |
Date: |
Sun, 04 Nov 2007 14:03:19 +0100 |
User-agent: |
Thunderbird 2.0.0.6 (Windows/20070728) |
Back in the 0.11.0 days I contributed some patches to Oskar aimed at
adding support for media types other than audio to gmediaserver.
Oskar decided to deal with the functionality that I proposed in the
following ways:
- Support for still images: Taken. This is now present in 0.12.0.
- Support for video: Not taken. This is absent from 0.12.0.
- Method of determining the media type contained in a particular file:
not taken. I had proposed a data-driven content type checking mechanism.
Oskar however decided to rely on libmagic for this purpose.
I have continued to maintain and run my own version of gmediaserver, for
the following reasons:
- The built-in content type scanning mechanism is several orders of
magnitude faster than the libmagic method. As my media collection is
quite large, and I routinely have gmediaserver re-scan the content
directory following the addition of new files, this makes for less of an
interruption to the enjoyment of my library.
- I have videos, not only audio files and photos.
For those of you who are interested in these features, please find
attached a patch against 0.12.0 that provides them. The only video
format currently supported is MPG, but this can trivially be extended to
other types as well.
Cheers, Jan
diff -u -r -X exclude gmediaserver-0.12.0/src/metadata.c
gmediaserver-0.12.0jc/src/metadata.c
--- gmediaserver-0.12.0/src/metadata.c 2006-08-31 21:48:01.000000000 +0200
+++ gmediaserver-0.12.0jc/src/metadata.c 2007-11-04 12:26:02.000000000
+0100
@@ -78,10 +78,143 @@
FILE_M3U,
FILE_EXTM3U,
FILE_JPG,
+ FILE_MPG,
FILE_UNKNOWN,
FILE_TYPES_COUNT,
} FileType;
+typedef struct {
+ enum {
+ TEST_END, TEST_IGNORE, TEST_STRING,
+ TEST_BITS, TEST_CRorLF, TEST_FEXT
+ } TestType;
+ enum {
+ TESTL_BOF, TESTL_REL, TESTL_FIND
+ } TestLocation;
+ uint8_t *TestString;
+ int TestCount;
+} Pattern;
+
+Pattern pattern_MP3[] =
+{
+ { TEST_STRING, TESTL_BOF, "\xff", 1 },
+ { TEST_BITS, TESTL_REL, "\xfe\xfa", 1 },
+ { TEST_END }
+};
+
+Pattern pattern_MP3_ID3[] =
+{
+ { TEST_STRING, TESTL_BOF, "ID3", 3 },
+ { TEST_END }
+};
+
+Pattern pattern_WMA[] =
+{
+ { TEST_STRING, TESTL_BOF, "\x30\x26\x26\x75", 4 },
+ { TEST_END }
+};
+
+Pattern pattern_JPG_JFIF[] =
+{
+ { TEST_STRING, TESTL_BOF, "\xff\xd8", 2 },
+ { TEST_IGNORE, TESTL_REL, NULL, 4 },
+ { TEST_STRING, TESTL_REL, "JFIF", 5 },
+ { TEST_END }
+};
+
+Pattern pattern_JPG_Exif[] =
+{
+ { TEST_STRING, TESTL_BOF, "\xff\xd8", 2 },
+ { TEST_IGNORE, TESTL_REL, NULL, 4 },
+ { TEST_STRING, TESTL_REL, "Exif", 5 },
+ { TEST_END }
+};
+
+Pattern pattern_RIFF_WAVE[] =
+{
+ { TEST_STRING, TESTL_BOF, "RIFF", 4 },
+ { TEST_IGNORE, TESTL_REL, NULL, 4 },
+ { TEST_STRING, TESTL_REL, "WAVE", 4 },
+ { TEST_END }
+};
+
+Pattern pattern_OGG[] =
+{
+ { TEST_STRING, TESTL_BOF, "OggS", 4 },
+ { TEST_IGNORE, TESTL_REL, NULL, 24 },
+ { TEST_STRING, TESTL_REL, "\1vorbis", 7 },
+ { TEST_END }
+};
+
+Pattern pattern_M4A[] =
+{
+ { TEST_IGNORE, TESTL_BOF, NULL, 4 },
+ { TEST_STRING, TESTL_REL, "ftypM4A", 7 },
+ { TEST_END }
+};
+
+Pattern pattern_EXTM3U[] =
+{
+ { TEST_STRING, TESTL_BOF, "#EXTM3U", 7 },
+ { TEST_CRorLF, TESTL_REL, NULL, 0 },
+ { TEST_END }
+};
+
+Pattern pattern_PLS[] =
+{
+ { TEST_STRING, TESTL_BOF, "[playlist]", 10 },
+ { TEST_CRorLF, TESTL_REL, NULL, 0 },
+ { TEST_END }
+};
+
+Pattern pattern_M3U[] =
+{
+ { TEST_FEXT, TESTL_BOF, ".m3u", 0 },
+ { TEST_END }
+};
+
+Pattern pattern_MPG[] =
+{
+ { TEST_STRING, TESTL_BOF, "\x00\x00\x01\xba", 4 },
+ { TEST_END }
+};
+
+Pattern pattern_MP3_liberal[] =
+{
+ { TEST_FEXT, TESTL_BOF, ".mp3", 0 },
+ { TEST_BITS, TESTL_FIND, "\xff\xff\xfe\xfa", 2 },
+ { TEST_END }
+};
+
+typedef struct {
+ FileType FT;
+ Pattern *pP;
+} PatternTableEntry;
+
+PatternTableEntry all_patterns[] =
+{
+ { FILE_MP3, &pattern_MP3[0] },
+ { FILE_MP3_ID3, &pattern_MP3_ID3[0] },
+ { FILE_WMA, &pattern_WMA[0] },
+ { FILE_JPG, &pattern_JPG_JFIF[0] },
+ { FILE_JPG, &pattern_JPG_Exif[0] },
+ { FILE_RIFF_WAVE, &pattern_RIFF_WAVE[0] },
+ { FILE_OGG, &pattern_OGG[0] },
+ { FILE_M4A, &pattern_M4A[0] },
+ { FILE_EXTM3U, &pattern_EXTM3U[0] },
+ { FILE_PLS, &pattern_PLS[0] },
+ { FILE_M3U, &pattern_M3U[0] },
+ { FILE_MPG, &pattern_MPG[0] },
+ /* Include the potentially expensive tests below, not above */
+ { FILE_MP3, &pattern_MP3_liberal[0] },
+ {
+ /* This marks the end of the list. Insert new items above, not below */
+ FILE_UNKNOWN, NULL
+ }
+};
+
+uint8_t buf[512];
+
typedef struct _InodeList InodeList;
struct _InodeList {
@@ -100,6 +233,7 @@
[FILE_M3U] = "audio/m3u",
[FILE_EXTM3U] = "audio/m3u",
[FILE_JPG] = "image/jpeg",
+ [FILE_MPG] = "video/mpeg",
[FILE_UNKNOWN] = "application/octet-stream",
};
@@ -114,6 +248,7 @@
[FILE_M3U] = "m3u",
[FILE_EXTM3U] = "m3u", /* possibly extm3u in the future */
[FILE_JPG] = "jpg",
+ [FILE_MPG] = "mpg",
[FILE_UNKNOWN] = "unknown",
};
@@ -128,7 +263,8 @@
[FILE_PLS] = "PLS playlist",
[FILE_M3U] = "Simple M3U playlist",
[FILE_EXTM3U] = "Extended M3U playlist",
- [FILE_JPG] = "JPEG image"
+ [FILE_JPG] = "JPEG image",
+ [FILE_MPG] = "MPEG-2 video"
};
static ItemClass file_type_item_classes[] = {
@@ -142,6 +278,7 @@
[FILE_M3U] = ITEM_PLAYLIST,
[FILE_EXTM3U] = ITEM_PLAYLIST,
[FILE_JPG] = ITEM_IMAGE,
+ [FILE_MPG] = ITEM_VIDEO,
};
static Entry *scan_entry(const char *fullpath, const char *name, int32_t
parent, int indent_size, InodeList *inl);
@@ -254,75 +391,181 @@
static FileType
check_file_content_type(const char *fullpath)
{
- const char *magic;
int fd;
- uint8_t buf[11];
- int c;
+ PatternTableEntry *curPTE;
- magic = magic_file(magic_cookie, fullpath);
- if (magic == NULL) {
- warn(_("%s: cannot identify file type: %s\n"),
quotearg(conv_filename(fullpath)), magic_error(magic_cookie));
- return FILE_UNKNOWN;
- }
-
- if (strcmp(magic, "application/octet-stream") != 0
- && strncmp(magic, "text/plain", 10) != 0) {
- struct {
- FileType id;
- char *mime;
- } mime_map[] = {
- { FILE_MP3, "audio/mpeg" }, /* XXX: FILE_MP3_ID3? */
- { FILE_WMA, "audio/x-ms-wma" },
- { FILE_RIFF_WAVE, "audio/x-wav" },
- { FILE_M4A, "video/mp4" },
- { FILE_M4A, "audio/mp4" },
- { FILE_OGG, "application/ogg" },
- { FILE_JPG, "image/jpeg" },
- { 0, },
- };
- for (c = 0; mime_map[c].mime != NULL; c++) {
- if (strcmp(magic, mime_map[c].mime) == 0)
- return mime_map[c].id;
- }
- }
+ memset(buf, 0, sizeof(buf)); /* Needed to avoid false matches in case file
shorter than buffer */
+ /* XXX: Use stdio instead of fd syscalls */
fd = open(fullpath, O_RDONLY);
if (fd < 0) {
warn(_("%s: cannot open for reading: %s\n"),
quotearg(conv_filename(fullpath)), errstr);
return FILE_UNKNOWN;
}
- if (!attempt_read_at(fd, 11, 0, buf, fullpath)) {
+
+ if (!attempt_read_at(fd, sizeof(buf), 0, buf, fullpath))
+ {
close(fd);
return FILE_UNKNOWN;
}
- /* Microsoft ASF */
- if (buf[0] == 0x30 && buf[1] == 0x26 && buf[2] == 0xb2 && buf[3] == 0x75) {
- close(fd); /* Ignore errors since we opened for reading */
- return FILE_WMA;
- }
+ close(fd); /* No further need for it */
+
+ for (curPTE = all_patterns; curPTE->FT != FILE_UNKNOWN; curPTE++)
+ {
+ Pattern *curP;
+ uint8_t *currentPos=buf;
+ enum { Inconclusive, PosMatch, NegMatch } testResult = Inconclusive;
+
+ for (curP = curPTE->pP;
+ (curP->TestType != TEST_END)
+ && (testResult == Inconclusive)
+ && (currentPos < buf+sizeof(buf)-curP->TestCount);
+ curP++)
+ {
+ switch (curP->TestLocation)
+ {
+ case TESTL_BOF:
+ currentPos = buf;
+ break;
+ case TESTL_REL:
+ break;
- /* Extended M3U */
- if (buf[0]=='#' && buf[1]=='E' && buf[2]=='X' && buf[3]=='T' &&
buf[4]=='M' && buf[5]=='3' && buf[6]=='U' && (buf[7]=='\r' || buf[7]=='\n')) {
- close(fd); /* Ignore errors since we opened for reading */
- return FILE_EXTM3U;
- }
- /* Playlist (PLS) */
- if (memcmp(buf, "[playlist]", 10) == 0 && (buf[10] == '\r' || buf[10] ==
'\n')) {
- close(fd); /* Ignore errors since we opened for reading */
- return FILE_PLS;
- }
- /* Simple M3U */
- if (ends_with_nocase(fullpath, ".m3u")) {
- close(fd); /* Ignore errors since we opened for reading */
- return FILE_M3U;
+ case TESTL_FIND:
+ /* This is currently only handled by the for loop within
+ * the TEST_BITS test below */
+ /* FIXME */
+ break;
+
+ // default:
+ /* FIXME */
+ }
+
+ switch (curP->TestType)
+ {
+ case TEST_END:
+ testResult = PosMatch;
+ break;
+
+ case TEST_IGNORE:
+ currentPos += curP->TestCount;
+ break;
+
+ case TEST_STRING:
+ if (0 != memcmp(currentPos, curP->TestString,
curP->TestCount))
+ testResult = NegMatch;
+
+ currentPos += curP->TestCount;
+ break;
+
+ case TEST_BITS:
+ if (curP->TestLocation == TESTL_FIND)
+ {
+ /* Check whether the bit pattern described by the
+ * TestString occurs anywhere between currentPos and
+ * the end of the buffer.
+ * TestString alternates between masks and test
+ * values: the test is as follows for each (mask,value)
+ * pair in TestString:
+ * ((*currentPos & *TestString) == *(TestString+1)) */
+ bool matchFound = false;
+
+ for (;
+ currentPos < buf+sizeof(buf)-curP->TestCount;
+ currentPos++)
+ {
+ uint8_t *b = curP->TestString;
+ uint8_t *cp = currentPos;
+ int count = curP->TestCount;
+
+ for (; count > 0; count--, b+=2)
+ {
+ if ((*cp++ & *b) != *(b+1))
+ {
+ /* The pattern does not occur here, but
+ * that does not mean that this is a
+ * negative match. It's only when we
+ * get to the end of the buffer that we
+ * can conclude that the pattern does not
+ * occur */
+ break;
+ }
+ }
+
+ if (0 == count)
+ {
+ /* We got to the end of the TestString. This
+ * means that we found the pattern at the
+ * current position. We can stop this test
+ * now and move onto the next-one */
+ currentPos += curP->TestCount;
+ matchFound = true;
+ break;
+ }
+ }
+
+ if ((!matchFound) && (currentPos >=
buf+sizeof(buf)-curP->TestCount))
+ {
+ /* We got to the end of the buffer. This means
+ * that the pattern did not occur anywhere within
+ * it. */
+ testResult = NegMatch;
+ }
+ }
+ else
+ {
+ /* Carry out the BITS test only once: at currentPos */
+ uint8_t *b;
+ int count;
+
+ for (b=curP->TestString, count=curP->TestCount;
+ count > 0;
+ count--, b+=2)
+ {
+ if ((*currentPos++ & *b) != *(b+1))
+ {
+ testResult = NegMatch;
+ break;
+ }
+ }
+ }
+ break;
+
+ case TEST_CRorLF:
+ if ((*currentPos != '\n') && (*currentPos != '\r'))
+ {
+ testResult = NegMatch;
+ }
+ currentPos++;
+ break;
+
+ case TEST_FEXT:
+ if (!ends_with_nocase(fullpath, curP->TestString))
+ {
+ testResult = NegMatch;
+ }
+ break;
+
+ // default:
+ /* FIXME */
+ }
+ }
+
+ if ((testResult == Inconclusive) && (curP->TestType == TEST_END))
+ testResult = PosMatch;
+
+ if (testResult == PosMatch)
+ {
+ return curPTE->FT;
+ }
}
- close(fd); /* Ignore errors since we opened for reading */
+ /* All tests performed, no match found */
return FILE_UNKNOWN;
}
+
#ifdef HAVE_ID3LIB
/* XXX: move this function to a more appropriate place, perhaps lib/iconvme? */
static char *
- [gmediaserver-devel] [Patch] Add support for MPG files; speed up initial start-up,
Jan Ceuleers <=