From a7158ceec00f14e680a885722cfe23761b4662d3 Mon Sep 17 00:00:00 2001 From: Philip Sequeira Date: Thu, 26 Jul 2018 23:01:35 -0400 Subject: [PATCH] demux: sort filenames naturally when playing a directory / archive --- demux/demux_libarchive.c | 3 +- demux/demux_playlist.c | 3 +- misc/natural_sort.c | 67 ++++++++++++++++++++++++++++++++++++++++ misc/natural_sort.h | 23 ++++++++++++++ wscript_build.py | 1 + 5 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 misc/natural_sort.c create mode 100644 misc/natural_sort.h diff --git a/demux/demux_libarchive.c b/demux/demux_libarchive.c index 51c4e46f03..3540082179 100644 --- a/demux/demux_libarchive.c +++ b/demux/demux_libarchive.c @@ -21,13 +21,14 @@ #include "common/common.h" #include "common/playlist.h" #include "stream/stream.h" +#include "misc/natural_sort.h" #include "demux.h" #include "stream/stream_libarchive.h" static int cmp_filename(const void *a, const void *b) { - return strcmp(*(char **)a, *(char **)b); + return mp_natural_sort_cmp(*(char **)a, *(char **)b); } static int open_file(struct demuxer *demuxer, enum demux_check check) diff --git a/demux/demux_playlist.c b/demux/demux_playlist.c index 897b83465f..b3881ef6b4 100644 --- a/demux/demux_playlist.c +++ b/demux/demux_playlist.c @@ -29,6 +29,7 @@ #include "options/path.h" #include "stream/stream.h" #include "osdep/io.h" +#include "misc/natural_sort.h" #include "demux.h" #define PROBE_SIZE (8 * 1024) @@ -284,7 +285,7 @@ static bool scan_dir(struct pl_parser *p, char *path, static int cmp_filename(const void *a, const void *b) { - return strcmp(*(char **)a, *(char **)b); + return mp_natural_sort_cmp(*(char **)a, *(char **)b); } static int parse_dir(struct pl_parser *p) diff --git a/misc/natural_sort.c b/misc/natural_sort.c new file mode 100644 index 0000000000..3e0bab0e3c --- /dev/null +++ b/misc/natural_sort.c @@ -0,0 +1,67 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . + */ + +#include "misc/ctype.h" + +#include "natural_sort.h" + +// Comparison function for an ASCII-only "natural" sort. Case is ignored and +// numbers are ordered by value regardless of padding. Two filenames that differ +// only in the padding of numbers will be considered equal and end up in +// arbitrary order. Bytes outside of A-Z/a-z/0-9 will by sorted by byte value. +int mp_natural_sort_cmp(const char *name1, const char *name2) +{ + while (name1[0] && name2[0]) { + if (mp_isdigit(name1[0]) && mp_isdigit(name2[0])) { + while (name1[0] == '0') + name1++; + while (name2[0] == '0') + name2++; + const char *end1 = name1, *end2 = name2; + while (mp_isdigit(*end1)) + end1++; + while (mp_isdigit(*end2)) + end2++; + // With padding stripped, a number with more digits is bigger. + if ((end1 - name1) < (end2 - name2)) + return -1; + if ((end1 - name1) > (end2 - name2)) + return 1; + // Same length, lexicographical works. + while (name1 < end1) { + if (name1[0] < name2[0]) + return -1; + if (name1[0] > name2[0]) + return 1; + name1++; + name2++; + } + } else { + if (mp_tolower(name1[0]) < mp_tolower(name2[0])) + return -1; + if (mp_tolower(name1[0]) > mp_tolower(name2[0])) + return 1; + name1++; + name2++; + } + } + if (name2[0]) + return -1; + if (name1[0]) + return 1; + return 0; +} diff --git a/misc/natural_sort.h b/misc/natural_sort.h new file mode 100644 index 0000000000..47b9a7a132 --- /dev/null +++ b/misc/natural_sort.h @@ -0,0 +1,23 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * mpv 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with mpv. If not, see . + */ + +#ifndef MP_NATURAL_SORT_H +#define MP_NATURAL_SORT_H + +int mp_natural_sort_cmp(const char *name1, const char *name2); + +#endif diff --git a/wscript_build.py b/wscript_build.py index 301e8819e4..2ad713efa5 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -317,6 +317,7 @@ def build(ctx): ( "misc/charset_conv.c" ), ( "misc/dispatch.c" ), ( "misc/json.c" ), + ( "misc/natural_sort.c" ), ( "misc/node.c" ), ( "misc/rendezvous.c" ), ( "misc/ring.c" ),