• Willkommen im Linux Club - dem deutschsprachigen Supportforum für GNU/Linux. Registriere dich kostenlos, um alle Inhalte zu sehen und Fragen zu stellen.

mp3s shuffeln mit Python

abgdf

Guru
Hi,

hier ein Python-Skript zum Abspielen von mp3s in zufälliger Reihenfolge in einem Verzeichnis und darunter. Es benötigt "mpg321":

http://packman.links2linux.de/package/mpg321

Kurzanleitung:
Das Python-Skript als "shuffle.py" abspeichern, "chmod +x shuffle.py" ausführen und in ein Verzeichnis mit mp3s kopieren. Dort das Skript mit "./shuffle.py" in der Konsole ausführen.
Die mp3s, die auf "mp3" enden, werden dann in zufälliger Reihenfolge abgespielt, aber jedes im Grundsatz nur einmal.
Man kann jederzeit wählen, ob man das Stück nochmal, das davor oder das danach abspielen will.
Man kann auch jederzeit ein besonderes Stück auswählen. Ansonsten: Drücke "h" für "Hilfe" :).

Ok, so ähnlich geht das auch mit "mpg321 -z" oder jedem anderen mp3-player, aber ich wollte halt noch mehr Kontrolle darüber haben und fand's so ganz bequem.
Code:
#!/usr/bin/env python

"""
ShufflePlayer


Searches a directory recursively for files ending with ".mp3" and
plays them as mp3s with external tool "mpg321" in random order.

Starts just on instance of "mpg321" in remote-control-mode.
Features key-input-control in terminal (press "h" for help).

Press "c" to play a certain song found.

Breaking the script with "STRG+c" may corrupt your terminal-settings;
cancel your xterm with "ALT+F4" then and start a new one.
Usually use "q" to quit.

(C) 2007 by abgdf@gmx.net, License: LGPL2.
"""

import os
import sys
import random
import tty
import termios
import fcntl
import time
import subprocess


class ShufflePlayer:

    def __init__(self):

        self.mp3list = self.getMp3List()

        self.nrmp3s = len(self.mp3list)
        if self.nrmp3s == 0:
            print
            print "No mp3s found in this directory."
            print
            sys.exit(1)

        random.seed()

        self.rorder = []
        for i in range(self.nrmp3s):
            self.rorder.append(i)
        random.shuffle(self.rorder)

        self.current = 0

        # Needed for self.checkKey():
        self.fd = sys.stdin.fileno()

        self.oldterm = termios.tcgetattr(self.fd)
        self.oldflags = fcntl.fcntl(self.fd, fcntl.F_GETFL)

        tty.setcbreak(sys.stdin.fileno())
        self.newattr = termios.tcgetattr(self.fd)
        self.newattr[3] = self.newattr[3] & ~termios.ICANON & ~termios.ECHO

        termios.tcsetattr(self.fd, termios.TCSANOW, self.newattr)
        fcntl.fcntl(self.fd, fcntl.F_SETFL, self.oldflags | os.O_NONBLOCK)

        # Start "mpg321" in remote-control-mode:

        self.player = subprocess.Popen(["mpg321", "-R", "abc"], stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE, bufsize = 0, close_fds = True)

        print
        print "ShufflePlayer: Press \"h\" for help."
        print

        self.playShuffle()


    def oldTerminalSettings(self):
            termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.oldterm)
            fcntl.fcntl(self.fd, fcntl.F_SETFL, self.oldflags)


    def newTerminalSettings(self):
            # tty.setcbreak(sys.stdin.fileno())
            termios.tcsetattr(self.fd, termios.TCSANOW, self.newattr)
            fcntl.fcntl(self.fd, fcntl.F_SETFL, self.oldflags | os.O_NONBLOCK)

    def checkKey(self):

        try:
            c = sys.stdin.read(1)
            return ord(c)

        except IOError:
            return 0

        except KeyboardInterrupt:
            self.oldTerminalSettings()
            self.player.stdin.write("QUIT\n")
            sys.exit(1)


    def getMp3List(self):
        # Get the names of all files in and
        # under a directory ending with "mp3".

        a = []

        for root, dirs, files in os.walk(os.getcwd()):
            for name in files:
                b = os.path.join(root, name)
                if b.endswith(".mp3"):
                    a.append(b)

        # Now we have a list of the names of all ".mp3"-files. We sort
        # them only by filenames, not by directory-names. But in the end,
        # we return a list containing their exact locations:

        b = []

        for i in a:
            b.append(self.cutDir(i))

        b.sort()

        c = []

        for i in b:
            for u in a:
                if i in u:
                    c.append(u)
                    a.remove(u)
                    break

        return c


    def playShuffle(self):

        while (self.current < self.nrmp3s):

            song = self.mp3list[self.rorder[self.current]]

            self.player.stdin.write("LOAD " + song + "\n")

            print "Playing: " +  self.cutDir(song)

            e = "a"

            while e[0:4] != "@P 0":

                e = self.player.stdout.readline()

                key = self.checkKey()

                if key != 0:
                    key = chr(key)
               
                if key == " ":
                    self.player.stdin.write("PAUSE\n")
                    self.oldTerminalSettings()
                    raw_input("Pause. Press Return to continue: ")
                    self.newTerminalSettings()
                    self.player.stdin.write("PAUSE\n")

                if key == "h":
                    self.showHelp()

                if key == "q":
                    self.oldTerminalSettings()
                    self.player.stdin.write("QUIT\n")
                    sys.exit(0)

                if key == "r":
                    self.current -= 1
                    break

                if key == "n":
                    break

                if key == "p":
                    self.current -= 2
                    break

                if key == "c":
                    self.player.stdin.write("PAUSE\n")
                    self.playCertainSong()
                    break

            self.current += 1

            if self.current < 0:
                self.current = self.nrmp3s - 1
         
        print "\nFinished. All songs played.\n"

        self.player.stdin.write("QUIT\n")
        self.oldTerminalSettings()


    def playCertainSong(self):

        self.oldTerminalSettings()

        print "\nPlay certain song:\n"

        for i in range(self.nrmp3s):
            print str(i + 1) + ". " + self.cutDir(self.mp3list[i])
        print

        sn = ""

        while not sn.isdigit():
            sn = raw_input('Song to play (q to quit) ? ')

            if sn == "q":
                self.newTerminalSettings()
                self.current -= 1
                print
                return

            if sn.isdigit():
                if sn == "0" or int(sn) > self.nrmp3s:
                    sn = ""

        sn = int(sn) - 1

        print
        print "Playing certain song: " + self.cutDir(self.mp3list[sn])
        print
        print "Press \"s\" to continue shuffling."
        print

        self.player.stdin.write("LOAD " +  self.mp3list[sn] + "\n")

        e = "a"

        self.newTerminalSettings()

        while e[0:4] != "@P 0":

            e = self.player.stdout.readline()

            key = self.checkKey()

            if key != 0:
                key = chr(key)

            if key == "s":
                break

            if key == "q":
                self.oldTerminalSettings()
                self.player.stdin.write("QUIT\n")
                sys.exit(0)


    def cutDir(self, a):
        a = a.split("/").pop()
        return a


    def showHelp(self):
        print
        print "ShufflePlayer:"
        print
        print "Options at startup are:"
        print "-r, --repeat\tShuffle endlessly."
        print
        print "Available keys are:"
        print "n\t\tNext song."
        print "p\t\tPrevious song."
        print "c\t\tCertain song."
        print "SPACE\t\tPause."
        print "h\t\tShow (this) help."
        print "q\t\tQuit ShufflePlayer."
        print



def main():
    if len(sys.argv) < 2:
        ShufflePlayer()
    else:
        if sys.argv[1] == "-r" or sys.argv[1] == "--repeat":
            while 1:
                ShufflePlayer()
        else:
            ShufflePlayer()


if __name__ == "__main__":
    main()
Viele Grüße
 
OP
A

abgdf

Guru
Update: Jetzt werden auch Unterverzeichnisse nach Dateien auf ".mp3" durchsucht.

Für mich jedenfalls allmählich fast so brauchbar wie "mp3blaster" :).

Viele Grüße
 
OP
A

abgdf

Guru
Hab' inzwischen auch noch eine alternative Version in Perl geschrieben (verwendet das Modul Audio::play::MPG123):
Code:
#!/usr/bin/perl

use warnings;
use strict;

use 5;

# Imports:

use Audio::Play::MPG123;

use Term::ReadKey;

use List::Util 'shuffle';

use File::Find;
use File::Basename;
# use File::Type;
use Cwd;

# Code-Start:

&Term::ReadKey::ReadMode(4);

my @mp3list = &getMp3List();

my $nrmp3s = @mp3list;

if ($nrmp3s == 0) {
    print "\nNo mp3s found in this directory.\n\n";
    &Term::ReadKey::ReadMode(0);
    exit(1);
}

my @rorder = (0 .. $nrmp3s - 1);
@rorder = shuffle(@rorder);

my $player = new Audio::Play::MPG123;

print "\nshufflemp3.pl: Press \"h\" for help.\n\n$nrmp3s mp3s found.\n\n";

my $current = 0;
my $song = "";
my $key = "";

while (1) {

    $song = $mp3list[$rorder[$current]];
    $player->load($song);

    print "Playing: " . &File::Basename::basename($song) . "\n";

    # Playback of one song:

    while($player -> state()) {

        $player -> poll(1);

        $key = &Term::ReadKey::ReadKey(-1);

        unless(defined $key) {
            $key = "";
        }

        if ($key eq " ") {

            &Term::ReadKey::ReadMode(0);

            $player -> pause();
           
            print "Pause. Press Return to continue: ";
            <>;
            &Term::ReadKey::ReadMode(4);
            $player -> pause();
        }

        if (ord($key) == 68) {
            # Left
            $player -> jump("-200");
            $player -> poll(1);
            print "Backwards.\n";
        }

        if (ord($key) == 67) {
            # Right
            $player -> jump("+200");
            $player -> poll(1);
            print "Forward.\n";
        }

        if ($key eq "h") {
            &showHelp();
        }

        if ($key eq "n") {
            $player -> stop();
            last;
        }

        if ($key eq "r") {
            $current--;
            $player -> stop();
            last;
        }

        if ($key eq "p") {
            $current -= 2;
            $player -> stop();
            last;
        }

        if ($key eq "c") {
            $player -> pause();
            $current = &certainSong();
            $player -> stop();
            last;
        }

        if ($key eq "q") {
            &Term::ReadKey::ReadMode(0);
            $player -> stop();
            $player -> stop_mpg123();
            print "\nBye.\n\n";
            exit(0);
        }
    }

    $current++;

    if ($current < 0) {
        $current = $nrmp3s - 1;
    }

    if ($current == $nrmp3s) {
        $current = 0;
    }
}
         
&Term::ReadKey::ReadMode(0);
$player -> stop_mpg123();
print "\nFinished. All songs played.\n\n";

sub certainSong {

    &Term::ReadKey::ReadMode(0);

    print "\nPlay certain song:\n\n";

    my $i;

    for $i (0 .. $nrmp3s - 1) {
        print $i + 1 . ". " . &File::Basename::basename($mp3list[$i]) . "\n";
    }

    print "\n";

    my $a;

    while(! defined $a || $a =~ /\D/   ||
                $a < 1 || $a > $nrmp3s) {

        print "Song to play ? ";
        chomp($a = <>);
    }

    $a--;

    &Term::ReadKey::ReadMode(4);

    for $i (0 .. $#rorder) {
        if ($rorder[$i] == $a) {
            $a = $i - 1;
            last;
        }
    }

    print "\n";

    return $a;
}

sub getMp3List {

    my $descend = 1;

    foreach (@ARGV) {
        if ($_ eq "-dir") {
            $descend = 0;
            last;
        }
    }

    # Get the names of all files in and
    # under a directory ending with "mp3".

    my @a;

    if ($descend) {

        # Without  File::Type-check:

        find({ wanted => sub { if (/\.mp3$/i) { push(@a, $File::Find::fullname); } }, follow_fast => 1, no_chdir => 1 }, &getcwd());
    }
    else {
        my @suf = ("mp3", "MP3", "Mp3", "mP3");
        foreach (@suf) {
            push(@a, <*.$_>);
        }
    }

=cut

    # With File::Type-check:

    my $fi;
    my $ft = File::Type->new();

    find({ wanted => sub { if ($_ !~ /\.mp3$/i) { return; }
                           $fi = $ft->checktype_filename($_);
                           if ($fi eq "audio/mp3" ||
                               $fi eq "application/octet-stream")
                             { push(@a, $File::Find::name); }
                         }, no_chdir => 1 }, &getcwd());
=cut

    # Removing double entries resulting from symbolic links:
    my %h;
    my $i;

    foreach $i (@a) {
        $h{$i} = 0;
    }
    @a = keys(%h);

    # Now we have a list of the names of all ".mp3"-files. We sort
    # them only by filenames, not by directory-names. But in the end,
    # we return a list containing their exact locations:

    my @b;

    foreach (@a) {
        push(@b, &File::Basename::basename($_));
    }

    @b = sort(@b);

    # We can't use a hash here, because some keys would be double:
    #
    #     /onedir/song.mp3
    # /anotherdir/song.mp3
    #

    my @c;
    my $u;

    for $i (@b) {
        for $u (0 .. $#a) {
            if ($a[$u] =~ m/\Q$i\E$/) {
                push(@c, $a[$u]);
                splice(@a, $u, 1);
                last;
            }
        }
    }

    return @c;
}

sub showHelp {

    my $help = <<HERE;

shufflemp3.pl: Available keys are:

    n           Play next song.
    p           Play previous song.
    LEFT        Go backwards in a song.
    RIGHT       Go forward in a song.
    c           Play certain song.
    SPACE       Pause.
    h           Show (this) help.
    q           Quit.

HERE

    print $help;
    print "Still playing: " . &File::Basename::basename($song) . "\n";
}

__END__

=head1 NAME

shufflemp3.pl - A shuffle-player for mp3-files (for Linux-terminals)

=head1 DESCRIPTION

Searches the current working directory for files ending with ".mp3", ".MP3", ".Mp3" or ".mP3" and plays them as mp3s with external tool "mpg123" in random order, looping. 

Starts just one instance of "mpg123" in remote-control-mode. Features direct key-input-control in terminal.

By default, the directory is searched recursively, but you can play just the mp3-files inside the directory (but not below it), by starting the script with

    shufflemp3.pl -dir

Available keys are:

    n           Play next song.
    p           Play previous song.
    LEFT        Go backwards in a song.
    RIGHT       Go forward in a song.
    c           Play certain song.
    SPACE       Pause.
    h           Show help.
    q           Quit.

One nice thing about this script is, sometimes you are surprised, with which mp3s it comes up, when you start it in an unfamiliar directory.

=head1 NOTES, CAVEATS

Unusual exits of the script may corrupt your current terminal-settings; just cancel your terminal with "ALT+F4" then and start a new one.

First, the script tries to find everything that ends with ".mp3", ".MP3", ".Mp3" or ".mP3". Then it tries to check with "File::Type", if the files found are really mp3-files. This check may not be 100%-accurate. So don't use the script, if you have documents, that are not mp3-files but end with something like ".mp3" (see above) inside or below your current working directory.

=head1 AUTHOR

abgdf, <abgdf@gmx.net>, 2009. Licence: Like Perl's.

=head1 SEE ALSO

Audio::Play::MPG123, Term::ReadKey, File::Find, File::Type.

=cut
Gruß

Edit (12.10.2011): Ausgebessert, daß manchmal Dateien doppelt gefunden wurden, wenn innerhalb des zu durchsuchenden Verzeichnisses Symlinks darauf vorhanden waren.
 
Oben