Python3 Script - funktioniert es so?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Xork2
User
Beiträge: 11
Registriert: Samstag 23. November 2019, 20:50

Hi!

Bezugnehmend auf mein Thema: viewtopic.php?t=47037
habe ich einen Freelancer gebeten, sich der Sache anzunehmen. Er hat daraufhin ein Script in Python3 geschrieben, welcher das Problem aus dem anderen Thread lösen sollte. Sprich:

- Es gibt vier Ordner + ein Jingle-Ordner
- es gibt verschiedene Zeitzonen (z.B. 6-12 Uhr), und dann ist jeweils ein bestimmter Ordner aktiv
- Aus dem derzeitign Ordner wird dann jeweils ein Song herausgeholt
- nach allen zwei Songs kommt ein Song aus dem Jingle-Ordner

Könntet ihr mal gucken, ob das so funktionieren würde?

Wenn der Code so funktioniert, reicht es dann, die derzeit aktiven Dateien einfach zu überschreiben oder muss der gesamte Server neugestartet werden?

Code: Alles auswählen

import os
import random
import datetime
import glob
import json


def songToPlay(directory, alreadyPlayed, totalRuns):
    os.chdir(directory)

    songsfiles = []
    for file in glob.glob("*.mp3"):
        songsfiles.append(file)
    length = len(songsfiles)
    count = 0
    while True:
        selectedSong = random.choice(songsfiles)
        if selectedSong not in alreadyPlayed:
            alreadyPlayed.append(selectedSong)
            totalRuns += 1
            if totalRuns > 2:
                totalRuns = 0
            return os.path.join(directory, selectedSong), alreadyPlayed, totalRuns
        if count == length:
            for file in glob.glob("*.mp3"):
                try:
                    alreadyPlayed.remove(file)
                except:
                    pass
            count = 0
        count += 1




def main():
    directory = os.path.dirname(os.path.abspath(__file__))
    DayRotation = os.path.join(directory, "DayRotation")
    EveRotation = os.path.join(directory, "EveRotation")
    MiddayRotation = os.path.join(directory, "MiddayRotation")
    NightRotation = os.path.join(directory, "NightRotation")
    jinglesDir = os.path.join(directory, "Jingles")
    currentHour = datetime.datetime.now().hour
    jsonFile = open('status.json', 'r')
    data = json.load(jsonFile)
    totalRuns = data["totalRuns"]
    alreadyPlayed = data["playedSongs"]

    if 0 <= currentHour <= 6 and totalRuns < 2:
        print('Playing Night Rotations')
        url, alreadyPlayed, totalRuns = songToPlay(NightRotation, alreadyPlayed, totalRuns)
    elif 6 < currentHour <= 12 and totalRuns < 2:
        print('Playing Midday Rotation')
        url, alreadyPlayed, totalRuns = songToPlay(MiddayRotation, alreadyPlayed, totalRuns)
    elif 12 < currentHour <= 18 and totalRuns < 2:
        print('Playing Day Rotation')
        url, alreadyPlayed, totalRuns = songToPlay(DayRotation, alreadyPlayed, totalRuns)
    elif 18 < currentHour <= 24 and totalRuns < 2:
        print('Playing Eve Rotation')
        url, alreadyPlayed, totalRuns = songToPlay(EveRotation, alreadyPlayed, totalRuns)
    elif totalRuns == 2:
        print('Playing jingles')
        url, alreadyPlayed, totalRuns = songToPlay(jinglesDir, alreadyPlayed, totalRuns)
    songsDict = {"totalRuns": totalRuns, "playedSongs": alreadyPlayed}
    print(url)
    os.chdir(directory)
    with open('status.json', 'w+') as outfile:
        json.dump(songsDict, outfile)


main()
Die status.json:

Code: Alles auswählen

{"totalRuns": 0, "playedSongs": []}
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

__blackjack__ hat Dir doch damals ein gutes Grundgerüst geschrieben. Warum hast Du das ignoriert?

Der Code hält sich nicht an die Namenskonventionen. Funktionen, wie Variablennamen schreibt man klein_mit_unterstrich. `chdir` sollte in einem ordentlichen Programm nicht vorkommen. Nackte Excepts ebensowenig. Listen, die man bei einem Funktionsaufruf ändert, sollten so auch deutlich in der Dokumentation gekennzeichnet sein. Wenn man die selbe List aber wieder als Rückgabewert zurückgibt, dann ist das nur verwirrend, und äußerst schlechter Programmierstil.

Was da mit count und der while-Schleife gemacht wird, ist auch sehr undurchsichtig.
`totalRuns` wird in der Funktion auch nur hochgezählt, und sonst nichts gemacht, gehört also nicht in die Funktion hinein.

Dass in der if-elif-Kaskade immer die selbe Teilbedingung vorkommt, ist umständlich, dass es keinen else-Zweig gibt, läßt die Frage offen, ob und was in diesem Fall passieren soll.

Dateien, die man öffnet, sollte man auch wieder schließen. Der Dateimodus `w+` ist selten sinnvoll, bei JSON-Dateien, wie hier, sogar definitiv falsch.

Code: Alles auswählen

import os
import random
import datetime
import glob
import json

BASEPATH = os.path.dirname(os.path.abspath(__file__))
STATUS_FILENAME = os.path.join(BASEPATH, 'status.json')
ROTATIONS = {
    6: "NightRotation",
    12: "MiddayRotation",
    18: "DayRotation",
    24: "EveRotation"
}

def find_song(directory, already_played_songs):
    already_played_songs = set(already_played_songs)
    song_filenames = set(glob.glob(os.path.join(directory, "*.mp3")))
    available_songs = song_filenames - already_played_songs
    if not available_songs:
        # reset already_played_songs
        available_songs = song_filenames
        already_played_songs -= song_filenames
    selected_song = random.choice(list(available_songs))
    already_played_songs.add(selected_song)
    return selected_song, list(already_played_songs)

def main():
    with open(STATUS_FILENAME) as json_file:
        status = json.load(json_file)
    total_runs = status["totalRuns"]
    already_played_songs = status["playedSongs"]
    if total_runs >= 2:
        total_runs = 0
        directory = "Jingles"
    else:
        total_runs += 1
        current_hour = datetime.datetime.now().hour
        for hour, directory in ROTATIONS.items():
            if current_hour <= hour:
                break
    print(f'Playing {directory} Rotations')
    selected_song, already_played_songs = find_song(os.path.join(BASEPATH, directory), already_played_songs)
    status = {
        "totalRuns": total_runs,
        "playedSongs": already_played_songs
    }
    print(selected_song)
    with open(STATUS_FILENAME, 'w') as outfile:
        json.dump(status, outfile)

if __name__ == '__main__':
    main()
nezzcarth
User
Beiträge: 1632
Registriert: Samstag 16. April 2011, 12:47

Xork2 hat geschrieben: Sonntag 15. Dezember 2019, 15:19 Wenn der Code so funktioniert, reicht es dann, die derzeit aktiven Dateien einfach zu überschreiben oder muss der gesamte Server neugestartet werden?
Ich habe das Gefühl, um das beantworten zu können, müsste man den breiteren Kontext kennen. Vielleicht übersehe ich etwas, aber das gezeigte Skript spielt ja gar nichts ab, sondern schreibt den Pfad zur gewählten mp3-Datei nur auf stdout. Wie funktioniert denn die Wiedergabe? Wird die Ausgabe des Skripts von einem anderen Programm eingelesen? (Falls ja: Sicher, dass die zusätzliche "Playing ..."-Ausgabe keine Probleme macht?) Oder ist die gezeigte eine gekürzte Fassung und das "echte" Skript spielt auch Musik ab? Und wie wird das Skript gestartet?
Xork2
User
Beiträge: 11
Registriert: Samstag 23. November 2019, 20:50

Sirius3 hat geschrieben: Sonntag 15. Dezember 2019, 16:13 __blackjack__ hat Dir doch damals ein gutes Grundgerüst geschrieben. Warum hast Du das ignoriert?

Der Code hält sich nicht an die Namenskonventionen. Funktionen, wie Variablennamen schreibt man klein_mit_unterstrich. `chdir` sollte in einem ordentlichen Programm nicht vorkommen. Nackte Excepts ebensowenig. Listen, die man bei einem Funktionsaufruf ändert, sollten so auch deutlich in der Dokumentation gekennzeichnet sein. Wenn man die selbe List aber wieder als Rückgabewert zurückgibt, dann ist das nur verwirrend, und äußerst schlechter Programmierstil.

Was da mit count und der while-Schleife gemacht wird, ist auch sehr undurchsichtig.
`totalRuns` wird in der Funktion auch nur hochgezählt, und sonst nichts gemacht, gehört also nicht in die Funktion hinein.

Dass in der if-elif-Kaskade immer die selbe Teilbedingung vorkommt, ist umständlich, dass es keinen else-Zweig gibt, läßt die Frage offen, ob und was in diesem Fall passieren soll.

Dateien, die man öffnet, sollte man auch wieder schließen. Der Dateimodus `w+` ist selten sinnvoll, bei JSON-Dateien, wie hier, sogar definitiv falsch.

Code: Alles auswählen

import os
import random
import datetime
import glob
import json

BASEPATH = os.path.dirname(os.path.abspath(__file__))
STATUS_FILENAME = os.path.join(BASEPATH, 'status.json')
ROTATIONS = {
    6: "NightRotation",
    12: "MiddayRotation",
    18: "DayRotation",
    24: "EveRotation"
}

def find_song(directory, already_played_songs):
    already_played_songs = set(already_played_songs)
    song_filenames = set(glob.glob(os.path.join(directory, "*.mp3")))
    available_songs = song_filenames - already_played_songs
    if not available_songs:
        # reset already_played_songs
        available_songs = song_filenames
        already_played_songs -= song_filenames
    selected_song = random.choice(list(available_songs))
    already_played_songs.add(selected_song)
    return selected_song, list(already_played_songs)

def main():
    with open(STATUS_FILENAME) as json_file:
        status = json.load(json_file)
    total_runs = status["totalRuns"]
    already_played_songs = status["playedSongs"]
    if total_runs >= 2:
        total_runs = 0
        directory = "Jingles"
    else:
        total_runs += 1
        current_hour = datetime.datetime.now().hour
        for hour, directory in ROTATIONS.items():
            if current_hour <= hour:
                break
    print(f'Playing {directory} Rotations')
    selected_song, already_played_songs = find_song(os.path.join(BASEPATH, directory), already_played_songs)
    status = {
        "totalRuns": total_runs,
        "playedSongs": already_played_songs
    }
    print(selected_song)
    with open(STATUS_FILENAME, 'w') as outfile:
        json.dump(status, outfile)

if __name__ == '__main__':
    main()
Danke dir für die hilfreichen Informationen. Im Gegenteil: Ich habe mich sehr gefreut, dass das alte Python 2 zu einem Python 3 Script umgewandelt worden ist. Ich habe das dem Freelancer zur Verfügung gestellt, und ja, er hatte es dann irgendwie komplett neu geschrieben und das vorhandene Python 3 gar nicht mehr genutzt. Ich kann mit vielem umgehen, leider gehört Python nicht dazu. Würde die Möglichkeit existieren, dass du es eventuell - mit den vorhandenen Scripts - richtig und korrekt realisieren könntest?

Wir haben mehrere Webradios, von denen die aller meisten über Software laufen. Dieses eine Webradio hier läuft seit Jahren über ein anderes Script und Programm, sodass es direkt über dieses Python ausgeführt wird.
Benutzeravatar
pillmuncher
User
Beiträge: 1482
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Kpommentare im Code:

Code: Alles auswählen

import os
import random
import datetime
import glob
import json


# Die Name der Funktionen und Variablen entsprechen allesamt nicht PEP8.
def songToPlay(directory, alreadyPlayed, total_runs):
    # os.chdir() wechselt das aktuelle Verzeichnis, in dem das Programm läuft.
    # Das brauch man hier überhaupt nicht, weil man auch glob.glob das
    # Verzeichnis in der glob mask mitgeben kann. Das führt dann auch nicht zu
    # irgendwelchen komischen Effekten, wie das bei os.chdir möglich ist.
    # Statt dessen sollte eine Liste von Songs als Argument mitübergeben werden,
    # dann wäre die Funktion auch testbar, ohne auf der Festplatte rumzurödeln.
    os.chdir(directory)

    # Es ergibt irgendwie keinen Sinn, hier in einer Schleife eine Liste zu
    # füllen, wenn die Ausgangs-Datenstruktur bereist eine Liste ist.
    songsfiles = []
    for file in glob.glob('*.mp3'):
        songsfiles.append(file)

    # Die Länge der Liste braucht (und sollte) man sich nicht in einer Variablen
    # zu merken, da man ja jederzeit len() aufrufen kann. Dann bekommt man auch
    # keine Probleme, wenn man Elemente der Liste löscht oder hinzufügt, denn
    # dann wäre die gespeichterte Länge ungleich der tatsächlichen, was zu
    # Problemen führen würde, wenn man sich darauf verlassen würde.
    length = len(songsfiles)
    # Zählvariablen von Hand verwalten? Echt jetzt?
    count = 0
    # Die Schleife ist viel zu kompliziert. Statt immer wieder per Zufall einen
    # song auszuwählen und dann zu schauen, ob er bereits gespielt wurde, kann
    # man doch einfach alle bereits gespielten Sings mittels Set-Logik
    # rauslöschen und dann per Zufall einen übrig gebliebenen Songs  auswählen.
    # Dann braucht man auch nicht globalen Zustand lokal ändern, indem man 
    # alreadyPlayed als Argument mitschleppt.
    # Wenn ich eine Funktion mit Argumenten aufrufem erwarte ich übrigens auch 
    # nicht, dass die Funktion diese Argumente verändert, außer der Name der
    # Funktion deutet das explizit an.
    # Interessanterweise sind Funktions-Parameter in Python unveränderbar, d.h.,
    # wenn man so einem Parameter in der Funktion einen neuen Wert zuweist,
    # spigelt sich das auf der Seites des Aufrufers nicht wieder. Deswegen
    # muss auch total_runs immer mittels return zurückgegeben werden.
    # Auch glob.glob() immer wieder in der Schleife aufzurufen, statt einmal vor
    # der Schleife, würde nur Sinn ergeben, wenn sich die Songs in dem
    # Verzeichnis während der Laufzit des Programms ändern können. Ist das so?
    while True:
        selectedSong = random.choice(songsfiles)
        if selectedSong not in alreadyPlayed:
            alreadyPlayed.append(selectedSong)
            total_runs += 1
            if total_runs > 2:
                total_runs = 0
            return os.path.join(directory, selectedSong), alreadyPlayed, total_runs
        if count == length:
            for file in glob.glob('*.mp3'):
                try:
                    alreadyPlayed.remove(file)
                except:
                    pass
            count = 0
        count += 1


def main():
    directory = os.path.dirname(os.path.abspath(__file__))
    DayRotation = os.path.join(directory, 'DayRotation')
    EveRotation = os.path.join(directory, 'EveRotation')
    MiddayRotation = os.path.join(directory, 'MiddayRotation')
    NightRotation = os.path.join(directory, 'NightRotation')
    jinglesDir = os.path.join(directory, 'Jingles')
    currentHour = datetime.datetime.now().hour
    # Dateien sollte man mittels eines with()-Blocks öffnen, dann werden sie
    # auch wieder geschlossen.
    jsonFile = open('status.json', 'r')
    data = json.load(jsonFile)
    total_runs = data['total_runs']
    alreadyPlayed = data['playedSongs']

    # Statt viermal auf total_runs < 2 zu testen, könnte man einmal am Anfang auf
    # total_runs == 2 testen denn alles darunter ist automatisch kleiner als 2.
    if 0 <= currentHour <= 6:
        print('Playing Night Rotations')
        url, alreadyPlayed, total_runs = songToPlay(NightRotation, alreadyPlayed, total_runs)
    elif 6 < currentHour <= 12:
        print('Playing Midday Rotation')
        url, alreadyPlayed, total_runs = songToPlay(MiddayRotation, alreadyPlayed, total_runs)
    elif 12 < currentHour <= 18:
        print('Playing Day Rotation')
        url, alreadyPlayed, total_runs = songToPlay(DayRotation, alreadyPlayed, total_runs)
    elif 18 < currentHour <= 24:
        print('Playing Eve Rotation')
        url, alreadyPlayed, total_runs = songToPlay(EveRotation, alreadyPlayed, total_runs)
    elif total_runs == 2:
        print('Playing jingles')
        url, alreadyPlayed, total_runs = songToPlay(jinglesDir, alreadyPlayed, total_runs)
    songsDict = {'total_runs': total_runs, 'playedSongs': alreadyPlayed}
    print(url)
    os.chdir(directory)
    with open('status.json', 'w+') as outfile:
        json.dump(songsDict, outfile)

# der Aufruf von main() sollte in einem if-__name__-Block stehen.
main()
So würde ich es machen:

Code: Alles auswählen

import datetime, glob, json, os, random


STATUS_FILENAME = 'status.json'
JINGLE_INTERVAL = 3
TIME_SEGMENT_NAMES = ['Night', 'Midday', 'Day', 'Eve']


def main(directory):
    with open(os.path.join(directory, STATUS_FILE), 'r') as status_file:
        status = json.load(stus_file)
    total_runs = (status['total_runs'] + 1) % JINGLE_INTERVAL
    already_played = set(status['playedSongs'])
    time_segment_index = datetime.datetime.now().hour / 6
    if total_runs:
        print('Playing',  TIME_SEGMENT_NAMES[time_segment_index], 'Rotation')
        sub_directory = TIME_SEGMENT_NAMES[time_segment_index] + 'Rotation'
    else:
        print('Playing Jingles')
        sub_directory = 'Jingles'
    song_directory = os.path.join(directory, sub_directory)
    song_names = set(glob.glob(os.path.join(song_directory, '*.mp3')))
    song_filename = random.choice(song_names - already_played)
    already_played.add(song_filename)
    print(song_filename)
    with open(os.path.join(directory, STATUS_FILENAME), 'w+') as status_file:
        json.dump(status_file, dict(totalRuns=total_runs, 
                                    playedSongs=list(already_played)))

    
if __name__ == '__main__':
    main(os.path.dirname(os.path.abspath(__file__)))
Ungetestet.
In specifications, Murphy's Law supersedes Ohm's.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Was fehlt Dir denn noch? Das Programm sollte doch alles tun, was Du willst.
Xork2
User
Beiträge: 11
Registriert: Samstag 23. November 2019, 20:50

pillmuncher hat geschrieben: Sonntag 15. Dezember 2019, 18:09 Kpommentare im Code:

Code: Alles auswählen

import os
import random
import datetime
import glob
import json


# Die Name der Funktionen und Variablen entsprechen allesamt nicht PEP8.
def songToPlay(directory, alreadyPlayed, total_runs):
    # os.chdir() wechselt das aktuelle Verzeichnis, in dem das Programm läuft.
    # Das brauch man hier überhaupt nicht, weil man auch glob.glob das
    # Verzeichnis in der glob mask mitgeben kann. Das führt dann auch nicht zu
    # irgendwelchen komischen Effekten, wie das bei os.chdir möglich ist.
    # Statt dessen sollte eine Liste von Songs als Argument mitübergeben werden,
    # dann wäre die Funktion auch testbar, ohne auf der Festplatte rumzurödeln.
    os.chdir(directory)

    # Es ergibt irgendwie keinen Sinn, hier in einer Schleife eine Liste zu
    # füllen, wenn die Ausgangs-Datenstruktur bereist eine Liste ist.
    songsfiles = []
    for file in glob.glob('*.mp3'):
        songsfiles.append(file)

    # Die Länge der Liste braucht (und sollte) man sich nicht in einer Variablen
    # zu merken, da man ja jederzeit len() aufrufen kann. Dann bekommt man auch
    # keine Probleme, wenn man Elemente der Liste löscht oder hinzufügt, denn
    # dann wäre die gespeichterte Länge ungleich der tatsächlichen, was zu
    # Problemen führen würde, wenn man sich darauf verlassen würde.
    length = len(songsfiles)
    # Zählvariablen von Hand verwalten? Echt jetzt?
    count = 0
    # Die Schleife ist viel zu kompliziert. Statt immer wieder per Zufall einen
    # song auszuwählen und dann zu schauen, ob er bereits gespielt wurde, kann
    # man doch einfach alle bereits gespielten Sings mittels Set-Logik
    # rauslöschen und dann per Zufall einen übrig gebliebenen Songs  auswählen.
    # Dann braucht man auch nicht globalen Zustand lokal ändern, indem man 
    # alreadyPlayed als Argument mitschleppt.
    # Wenn ich eine Funktion mit Argumenten aufrufem erwarte ich übrigens auch 
    # nicht, dass die Funktion diese Argumente verändert, außer der Name der
    # Funktion deutet das explizit an.
    # Interessanterweise sind Funktions-Parameter in Python unveränderbar, d.h.,
    # wenn man so einem Parameter in der Funktion einen neuen Wert zuweist,
    # spigelt sich das auf der Seites des Aufrufers nicht wieder. Deswegen
    # muss auch total_runs immer mittels return zurückgegeben werden.
    # Auch glob.glob() immer wieder in der Schleife aufzurufen, statt einmal vor
    # der Schleife, würde nur Sinn ergeben, wenn sich die Songs in dem
    # Verzeichnis während der Laufzit des Programms ändern können. Ist das so?
    while True:
        selectedSong = random.choice(songsfiles)
        if selectedSong not in alreadyPlayed:
            alreadyPlayed.append(selectedSong)
            total_runs += 1
            if total_runs > 2:
                total_runs = 0
            return os.path.join(directory, selectedSong), alreadyPlayed, total_runs
        if count == length:
            for file in glob.glob('*.mp3'):
                try:
                    alreadyPlayed.remove(file)
                except:
                    pass
            count = 0
        count += 1


def main():
    directory = os.path.dirname(os.path.abspath(__file__))
    DayRotation = os.path.join(directory, 'DayRotation')
    EveRotation = os.path.join(directory, 'EveRotation')
    MiddayRotation = os.path.join(directory, 'MiddayRotation')
    NightRotation = os.path.join(directory, 'NightRotation')
    jinglesDir = os.path.join(directory, 'Jingles')
    currentHour = datetime.datetime.now().hour
    # Dateien sollte man mittels eines with()-Blocks öffnen, dann werden sie
    # auch wieder geschlossen.
    jsonFile = open('status.json', 'r')
    data = json.load(jsonFile)
    total_runs = data['total_runs']
    alreadyPlayed = data['playedSongs']

    # Statt viermal auf total_runs < 2 zu testen, könnte man einmal am Anfang auf
    # total_runs == 2 testen denn alles darunter ist automatisch kleiner als 2.
    if 0 <= currentHour <= 6:
        print('Playing Night Rotations')
        url, alreadyPlayed, total_runs = songToPlay(NightRotation, alreadyPlayed, total_runs)
    elif 6 < currentHour <= 12:
        print('Playing Midday Rotation')
        url, alreadyPlayed, total_runs = songToPlay(MiddayRotation, alreadyPlayed, total_runs)
    elif 12 < currentHour <= 18:
        print('Playing Day Rotation')
        url, alreadyPlayed, total_runs = songToPlay(DayRotation, alreadyPlayed, total_runs)
    elif 18 < currentHour <= 24:
        print('Playing Eve Rotation')
        url, alreadyPlayed, total_runs = songToPlay(EveRotation, alreadyPlayed, total_runs)
    elif total_runs == 2:
        print('Playing jingles')
        url, alreadyPlayed, total_runs = songToPlay(jinglesDir, alreadyPlayed, total_runs)
    songsDict = {'total_runs': total_runs, 'playedSongs': alreadyPlayed}
    print(url)
    os.chdir(directory)
    with open('status.json', 'w+') as outfile:
        json.dump(songsDict, outfile)

# der Aufruf von main() sollte in einem if-__name__-Block stehen.
main()
So würde ich es machen:

Code: Alles auswählen

import datetime, glob, json, os, random


STATUS_FILENAME = 'status.json'
JINGLE_INTERVAL = 3
TIME_SEGMENT_NAMES = ['Night', 'Midday', 'Day', 'Eve']


def main(directory):
    with open(os.path.join(directory, STATUS_FILE), 'r') as status_file:
        status = json.load(stus_file)
    total_runs = (status['total_runs'] + 1) % JINGLE_INTERVAL
    already_played = set(status['playedSongs'])
    time_segment_index = datetime.datetime.now().hour / 6
    if total_runs:
        print('Playing',  TIME_SEGMENT_NAMES[time_segment_index], 'Rotation')
        sub_directory = TIME_SEGMENT_NAMES[time_segment_index] + 'Rotation'
    else:
        print('Playing Jingles')
        sub_directory = 'Jingles'
    song_directory = os.path.join(directory, sub_directory)
    song_names = set(glob.glob(os.path.join(song_directory, '*.mp3')))
    song_filename = random.choice(song_names - already_played)
    already_played.add(song_filename)
    print(song_filename)
    with open(os.path.join(directory, STATUS_FILENAME), 'w+') as status_file:
        json.dump(status_file, dict(totalRuns=total_runs, 
                                    playedSongs=list(already_played)))

    
if __name__ == '__main__':
    main(os.path.dirname(os.path.abspath(__file__)))
Ungetestet.

Vielen lieben Dank für die schnelle Rückantwort und für das Anpassen.

Habe mir die Kommentare, die du geschrieben hast, durchgelesen.

Bei:
der Schleife, würde nur Sinn ergeben, wenn sich die Songs in dem
# Verzeichnis während der Laufzit des Programms ändern können

Also ab und an werden natürlich neue Titel hochgeladen und ältere Titel entfernt, sodass sich die darin enthaltenen Songs tatsächlich auch ändern können. Was meinst du mit "während der Laufzeit des Programms" ? Die Zeit während der jeweilige Ordner aktiv ist ?
nezzcarth
User
Beiträge: 1632
Registriert: Samstag 16. April 2011, 12:47

Xork2 hat geschrieben: Sonntag 15. Dezember 2019, 18:24 Also ab und an werden natürlich neue Titel hochgeladen und ältere Titel entfernt, sodass sich die darin enthaltenen Songs tatsächlich auch ändern können. Was meinst du mit "während der Laufzeit des Programms" ? Die Zeit während der jeweilige Ordner aktiv ist ?
Das gezeigte Skript wird gestartet, sucht eine zufällige MP3-Datei aus und schreibt diese, zusammen mit einer zweiten Nachricht auf die Standardausgabe. Das sollte in der Regel wenige Sekunden dauern, und um den Zeitraum geht es. Das Skript läuft nicht permanent durch, sondern wird anscheinend von außen immer wieder neu gestartet.
Xork2
User
Beiträge: 11
Registriert: Samstag 23. November 2019, 20:50

nezzcarth hat geschrieben: Sonntag 15. Dezember 2019, 18:30
Xork2 hat geschrieben: Sonntag 15. Dezember 2019, 18:24 Also ab und an werden natürlich neue Titel hochgeladen und ältere Titel entfernt, sodass sich die darin enthaltenen Songs tatsächlich auch ändern können. Was meinst du mit "während der Laufzeit des Programms" ? Die Zeit während der jeweilige Ordner aktiv ist ?
Das gezeigte Skript wird gestartet, sucht eine zufällige MP3-Datei aus und schreibt diese, zusammen mit einer zweiten Nachricht auf die Standardausgabe. Das sollte in der Regel wenige Sekunden dauern, und um den Zeitraum geht es.
Okay, in diesem Zeitraum höchstwahrscheinlich nicht. Ohnehin wird dann darauf geachtet, dass Songs aus einem Ordner, welcher derzeit aktiv ist, nicht getauscht werden, sondern immer nur Songs getauscht werden bei Ordnern, die derzeit nicht aktiv sind.

Reicht es bei Python Skripten, wenn ich die Dateien ersetze oder muss dazu ein Restart des gesamten Servers erfolgen?
Muss nach Anpassung des Scripts eigentlich auch die status.json geändert werden oder kann diese so bleiben?
Xork2
User
Beiträge: 11
Registriert: Samstag 23. November 2019, 20:50

Ich habe mir nochmal den Code angeschaut

Code: Alles auswählen

import datetime, glob, json, os, random


STATUS_FILENAME = 'status.json'
JINGLE_INTERVAL = 3
TIME_SEGMENT_NAMES = ['Night', 'Midday', 'Day', 'Eve']


def main(directory):
    with open(os.path.join(directory, STATUS_FILE), 'r') as status_file:
        status = json.load(stus_file)
    total_runs = (status['total_runs'] + 1) % JINGLE_INTERVAL
    already_played = set(status['playedSongs'])
    time_segment_index = datetime.datetime.now().hour / 6
    if total_runs:
        print('Playing',  TIME_SEGMENT_NAMES[time_segment_index], 'Rotation')
        sub_directory = TIME_SEGMENT_NAMES[time_segment_index] + 'Rotation'
    else:
        print('Playing Jingles')
        sub_directory = 'Jingles'
    song_directory = os.path.join(directory, sub_directory)
    song_names = set(glob.glob(os.path.join(song_directory, '*.mp3')))
    song_filename = random.choice(song_names - already_played)
    already_played.add(song_filename)
    print(song_filename)
    with open(os.path.join(directory, STATUS_FILENAME), 'w+') as status_file:
        json.dump(status_file, dict(totalRuns=total_runs, 
                                    playedSongs=list(already_played)))

    
if __name__ == '__main__':
    main(os.path.dirname(os.path.abspath(__file__)))
Da ich mit Python ja nicht so bewandert bin: Wo genau ist da geregelt, zu welcher Uhrzeit welcher Ordner gespielt werden muss?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Zeile `time_segment_index = datetime.datetime.now().hour / 6` enthält ja die aktuelle Stunde, die für Python 3 übrigens mit // geschrieben sein muß.
Dem Skript von pillmuncher fehlt übrigens der Reset der already_played-Liste, so dass irgendwann das Programm abstürzt, weil es keine Songs mehr übrig hat.
Das `w+`-Problem ist übrigens auch enthalten.

Die Frage ist auch, ob die „Nacht”-Lieder bis 5:59Uhr oder bis 6:59Uhr gespielt werden sollen.

Nochmal die Frage: wie bindet sich das Programm in den ganzen anderen Server ein? War ändert wann die Songliste?
Benutzeravatar
pillmuncher
User
Beiträge: 1482
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@Sirius3: Gut, dass du die Fehler gesehen hast. Einen anderen hab ich auch noch gefunden. Hier eine korrigierte Version:

Code: Alles auswählen

import datetime, glob, json, os, random


STATUS_FILENAME = 'status.json'
JINGLE_INTERVAL = 3
TIME_SEGMENT_NAMES = ['Night', 'Midday', 'Day', 'Eve']


def main(directory):
    with open(os.path.join(directory, STATUS_FILE), 'r') as status_file:
        status = json.load(stus_file)
    total_runs = (status['total_runs'] + 1) % JINGLE_INTERVAL
    already_played = set(status['playedSongs'])
    time_segment_index = datetime.datetime.now().hour // 6
    if total_runs:
        print('Playing',  TIME_SEGMENT_NAMES[time_segment_index], 'Rotation')
        sub_directory = TIME_SEGMENT_NAMES[time_segment_index] + 'Rotation'
    else:
        print('Playing Jingles')
        sub_directory = 'Jingles'
    song_directory = os.path.join(directory, sub_directory)
    song_names = set(glob.glob(os.path.join(song_directory, '*.mp3')))
    if not song_names:
        raise ValueError(
            f"Directory {song_directory} doesn't contain any mp3 files.")
    playable_songs = song_names - already_played
    if playable_songs:
        song_filename = random.choice(playable_songs)
    else:
        song_filename = random.choice(song_names)
        already_played = set()
    already_played.add(song_filename)
    print(song_filename)
    with open(os.path.join(directory, STATUS_FILENAME), 'w') as status_file:
        json.dump(status_file, dict(totalRuns=total_runs, 
                                    playedSongs=list(already_played)))

    
if __name__ == '__main__':
    main(os.path.dirname(os.path.abspath(__file__)))
Wieder ungetestet.
In specifications, Murphy's Law supersedes Ohm's.
Xork2
User
Beiträge: 11
Registriert: Samstag 23. November 2019, 20:50

Sirius3 hat geschrieben: Sonntag 15. Dezember 2019, 18:50 Die Zeile `time_segment_index = datetime.datetime.now().hour / 6` enthält ja die aktuelle Stunde, die für Python 3 übrigens mit // geschrieben sein muß.
Dem Skript von pillmuncher fehlt übrigens der Reset der already_played-Liste, so dass irgendwann das Programm abstürzt, weil es keine Songs mehr übrig hat.
Das `w+`-Problem ist übrigens auch enthalten.

Die Frage ist auch, ob die „Nacht”-Lieder bis 5:59Uhr oder bis 6:59Uhr gespielt werden sollen.

Nochmal die Frage: wie bindet sich das Programm in den ganzen anderen Server ein? War ändert wann die Songliste?
Danke für die ganzen Hinweise.
Wegen deiner Frage: Ich glaube, ihr macht es vielleicht zu kompliziert. In dem anderen Thread hatte ich ja ein Skript gepostet, welches dann von einem freundlichen Forenuser hier auf Python 3 umgestellt wurde. Das Script hat ja so funktioniert, dass es aus zwei Ordnern (1 Ordner ging also von 0 - 10, 2 Ordner von 10 - 0) die Songs holte und abspielte. Jetzt war für mich nur wichtig, dieses Script einfach auf vier Ordner und entsprechend den Zeiten zu erweitern, sodass ich, wenn ich das neue Script nutze, nur noch die jeweiligen Ordner erstelle, die im selben Verzeichnis wie das Script selbst sind, erstelle und dementsprechend auch das neue Script mit dem alten ersetze.

Wäre das alles so möglich? Ich würde sagen ja
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Trotz mehrfachem Nachfragen hast Du noch nicht verraten, was denn nun noch zu tun ist.
Ich finde weder meine noch pillmunchers Lösung jetzt zu kompliziert. Es sind halt nur noch Fragen offen, die Du nicht beantwortest.
Welche Zeiten sollen es denn nun sein?
Wann sollen sich Songs wiederholen?
Xork2
User
Beiträge: 11
Registriert: Samstag 23. November 2019, 20:50

Es war nicht meine Absicht, eure Lösungen als zu kompliziert zu bezeichnen. Vielmehr ging es mir darum, dass bereits vorhandene Script so zu erweitern, dass es statt 2 Ordner 4 Ordner gibt.

Schau mal hier: viewtopic.php?f=1&t=47037&p=356584#p356584

Da habe ich das derzeitig laufende Script aktiv. Dieses soll ersetzt werden. Ein freundlicher User hatte dieses Script bereits auf Python 3 umgewandelt.

Ich hoffe, ich kann deine Fragen beantworten:

Jeder Ordner hat ein Zeitrahmen, dieser geht sechs Stunden. In jedm dieser Ordner werden so etwa 30 – 50 Songs sein, welche ausgespielt werden. Es sollen zwei Songs aus dem Ordner gewählt werden, welcher gerade lt. Zeitangabe (z. B. 0 – 6 Uhr) aktiv ist. Nach zwei Songs soll ein Titel aus dem „Jingle“-Ordner wiedergegeben werden.

Ein identischer Song darf nur einmal die Stunde gespielt werden. Bei Jingles gibt es diese Regelung hingegen nicht.

Wenn nun z. B. der Ordner der Night Rotation von 00:00 bis 06:00 Uhr geht, dann ist hiermit 05:59 Uhr gemeint.

Wie sich das Script in das System ansich integriert kann ich nicht direkt beantworten. Dieses Script heißt im Ursprung „playlist.py“ und in dem Verzeichnis, in welchem die Datei ist, sind auch die Ordner, aus denen die „playlist.py“ die Songs auswählt.

Geändert weren die Songs händisch durch das Team (FTP-Programm). D.h. Songs werden aus einem Ordner gelöscht und durch neue ersetzt. Jedoch immer nur dann, wenn der derzeitige Ordner nicht aktiv ist (also z.B. wird die Night Rotation dann zur Mittagszeit geändert, also nach 06:00 Uhr morgens).

Vielen Dank für eure Hilfe bislang. Ich hoffe, ich konnte die Fragen beantworten? 😊
nezzcarth
User
Beiträge: 1632
Registriert: Samstag 16. April 2011, 12:47

Xork2 hat geschrieben: Montag 16. Dezember 2019, 20:52 Wegen deiner Frage: Ich glaube, ihr macht es vielleicht zu kompliziert.
Bitte entschuldige, wenn das unfreundlich klingen mag, aber ich glaube umgekehrt, dass du vielleicht nicht im Detail verstanden hast, wie die Skripte (sowohl das Ausgangsskript, das BlackJack dir (um)gebaut hat, als auch die aktuelle Version) funktionieren. Das Skript spielt nichts ab. Das einzige, was es tut, ist zufällig eine MP3-Datei auszuwählen und diese als Text auszugeben. Daher ist es in jedem Fall Teil eines komplexeren Setups (also zum Beispiel icecast als Streaming Server und Ices als Client oder was man da heute so nimmt). Und um dir zu beantworten, ob und was du da neustarten musst oder ersetzen kannst, muss man etwas mehr über die "Gesamtkonstruktion" wissen.

Ein üblicher Mechanismus, wie das Skript eingebunden sein kann, ist zum Beispiel, dass man in der Config des Streaming Clients ein Skript angeben kann, das jedes mal, wenn eine neue MP3 abgespielt werden soll, als externer Prozess aufgerufen und dessen Ausgabe von dem Client anschließend ausgewertet wird (in dem Fall sollte man übrigens, wie oben erwähnt, das zusätzliche print("Playing ...") wieder entfernen). Wenn das so ist, ist es gut möglich, dass es genügt, wenn du die playlist.py einfach (zu einem günstigen Zeitpunkt) austauschst. Wenn das Skript aber irgendwie von einer anderen Software in den Speicher geladen wird (auch wenn ich das für eher unwahrscheinlich halte), kann es genau so gut sein, dass du die Serversoftware neu starten musst.

Weiterhin sind alle Varianten des Skripts, die bisher in diesem und auch im alten Thread vorgestellt worden sind, eher minimalistisch. Zum Beispiel muss die Status-Json mit der entsprechenden Struktur irgendwie von außen erzeugt werden; das passiert nicht von alleine.
Xork2
User
Beiträge: 11
Registriert: Samstag 23. November 2019, 20:50

nezzcarth hat geschrieben: Montag 16. Dezember 2019, 22:13
Xork2 hat geschrieben: Montag 16. Dezember 2019, 20:52 Wegen deiner Frage: Ich glaube, ihr macht es vielleicht zu kompliziert.
Bitte entschuldige, wenn das unfreundlich klingen mag, aber ich glaube umgekehrt, dass du vielleicht nicht im Detail verstanden hast, wie die Skripte (sowohl das Ausgangsskript, das BlackJack dir (um)gebaut hat, als auch die aktuelle Version) funktionieren. Das Skript spielt nichts ab. Das einzige, was es tut, ist zufällig eine MP3-Datei auszuwählen und diese als Text auszugeben. Daher ist es in jedem Fall Teil eines komplexeren Setups (also zum Beispiel icecast als Streaming Server und Ices als Client oder was man da heute so nimmt). Und um dir zu beantworten, ob und was du da neustarten musst oder ersetzen kannst, muss man etwas mehr über die "Gesamtkonstruktion" wissen.

Ein üblicher Mechanismus, wie das Skript eingebunden sein kann, ist zum Beispiel, dass man in der Config des Streaming Clients ein Skript angeben kann, das jedes mal, wenn eine neue MP3 abgespielt werden soll, als externer Prozess aufgerufen und dessen Ausgabe von dem Client anschließend ausgewertet wird (in dem Fall sollte man übrigens, wie oben erwähnt, das zusätzliche print("Playing ...") wieder entfernen). Wenn das so ist, ist es gut möglich, dass es genügt, wenn du die playlist.py einfach (zu einem günstigen Zeitpunkt) austauschst. Wenn das Skript aber irgendwie von einer anderen Software in den Speicher geladen wird (auch wenn ich das für eher unwahrscheinlich halte), kann es genau so gut sein, dass du die Serversoftware neu starten musst.

Weiterhin sind alle Varianten des Skripts, die bisher in diesem und auch im alten Thread vorgestellt worden sind, eher minimalistisch. Zum Beispiel muss die Status-Json mit der entsprechenden Struktur irgendwie von außen erzeugt werden; das passiert nicht von alleine.
Nein, alles gut. :)

Du schriebst, die Skripte hier im Forum seien minimalistisch. Das derzeitige aktivierte Skript müsste folglich noch minimalistischer sein, oder? Demnach finde ich, sind wir ein gutes Stück voran gekommen, um flexibler zu agieren. Gibt es eurer Meinung denn Funktionen, die vielleicht auch noch sinnvoll wären, sie zu implementieren? Bislang ist es so, wie du geschrieben hast: Das Skript wählt die Songs jeweils aus, schreibt sie in die json-Datei und prinzipiell wäre es das. Also das aktuelle Script schreibt es nicht in eine json, sondern in die status.txt-Datei.

Zur Frage des Programms, da nutzen wir momentan Shoutcast.

Für mich als absoluten Python-Laien klang es erst so, als wäre das Skript, welches der Freelancer gemacht hat, nicht ganz so zielführend. Die hier geposteten Scripts von euch scheinen mir auch ein wenig strukturierter auszusehen.

Prinzipiell bin ich mit dem Skript zufrieden, und freue mich, wenn es funktioniert.
Falls mir jemand noch die Frage beantworten kann, ob direkt alles neugestartet werden muss oder nicht, wäre es schön. Andernfalls probiere ich es einfach in der Nacht von Donnerstag auf Freitag bzw. Freitag auf Samstag aus. An dem Server hängt ziemlich viel, daher wäre es eigentlich schön gewesen, würde er nicht komplett neugestartet werden müssen.

Ich möchte hier ganz lieben Dank an alle sagen. Das hat mich ein großes Stück weiter gebracht.
Antworten