|
TK87
Anmeldungsdatum: 8. Juli 2019
Beiträge: 266
|
UlfZibis schrieb: TK87 schrieb: Falsch, Open Source!
Echt, wo kann man die finden? Bei der URL "microsoft.com" bin ich da erst mal gar nicht drauf gekommen, dass es die geben könnte.
Steht eigentlich schon in dem Link den ich gepostet habe:
https://github.com/powershell/powershell/
Und bist Du sicher, dass da keine binären BLOB's drin sind, so wie es bei VSCode der Fall ist.
Ja.
Probier da mal dir /B. Wetten, dass Du dann nicht den Inhalt von Verzeichnis B angezeigt bekommst.
Doch, damit würdest du Verzeichnis B auflisten. Du verwechselst das mit CMD. Bei Powershell werden die Parameter nicht mit "/", sondern mit "-" angegeben und sind eher an der Sprache editiert, z.B. "-recurse" für rekursives auflisten. Die Parameter können aber auch gekürzt werden (also z.B. "-r"), solange sie noch eindeutig sind.
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4735
|
@UlfZibis: [a, *(b, c), d] ist letztlich das gleiche wie [a, b, c, d], ich habe das bei Optionen mit zusätzlichen Wert benutzt, um das deutlicher zu machen, dass die beiden Elemente zusammengehören. Sonst würde mir mein Editor/das Formatierungsplugin die auch untereinander setzen. Oder ich müsste einen speziellen Kommentar verwenden, damit das Formatierungsprogramm diese Liste nicht formatiert. Der * in einer literalen Liste, gefolgt von einem iterierbaren Wert, ”entpackt” die Elemente als würden sie an der Stelle in der Liste stehen. Zwei re.search() sind zumindest nicht performanter als nur einmal die Ausgabe zu durchsuchen. Ich fand das eigentlich recht einfach nach beiden zu suchen. Es funktioniert auch unabhängig von der Reihenfolge in der das in der Ausgabe auftaucht. Man kann den CalledProcessError behandeln und beispielsweise den Dateipfad und den Rückgabecode von ffmpeg in einer Liste speichern und die am Ende ausgeben (falls sie Inhalt hat). Das Ausnahme-Objekt hat auch die Ausgabe die ffmpeg gemacht hat, falls man die auch aufheben und später ausgeben möchte. Ungetestet:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 | #!/usr/bin/env python3
import re
import subprocess
from pathlib import Path
def detect_volumes(flac_file_path):
result = subprocess.run(
[
"ffmpeg",
"-hide_banner",
"-nostdin",
*("-i", str(flac_file_path)),
*("-af", "volumedetect"),
*("-f", "null"),
"/dev/null",
],
capture_output=True,
check=True,
)
volumes = {
match[1]: float(match[2])
for match in re.finditer(
rb"(mean|max)_volume: (.*?) dB", result.stderr
)
}
return (volumes[b"mean"], volumes[b"max"])
def main():
flac_directory_count = 0
flac_file_paths_with_errors = []
directory_paths = sorted(
path for path in Path("Ablage").rglob("*") if path.is_dir()
)
for directory_path in directory_paths:
flac_file_paths = sorted(
directory_path.glob("*.flac", case_sensitive=False)
)
for flac_file_path in flac_file_paths:
print(flac_file_path)
try:
mean_volume, max_volume = detect_volumes(flac_file_path)
except subprocess.CalledProcessError as error:
flac_file_paths_with_errors.append(
(flac_file_path, error.returncode)
)
else:
print(
f"mean volume: {mean_volume} dB, max_volume: {max_volume} dB"
)
if flac_file_paths:
flac_directory_count += 1
print("files:", len(flac_file_paths))
print()
print("Number of dirs total:", len(directory_paths))
print("Number of dirs with flac files:", flac_directory_count)
print("\n")
if flac_file_paths_with_errors:
print(f"Flac files with errors ({len(flac_file_paths_with_errors)}):")
for flac_file_path, returncode in flac_file_paths_with_errors:
print(flac_file_path, "with ffmpeg return code", returncode)
if __name__ == "__main__":
main()
|
PowerShell ist halt weniger auf kurze Kommandos ausgelegt, die gehen davon aus, dass man eine moderne IDE mit Autovervollständigung hat, und nicht alles immer wieder komplett eintippen muss. Dafür ist es dann oft lesbarer, weil weniger kryptische Abkürzungen. Und es sind auch nicht mehr nur einfache Zeichenketten die da geliefert werden, sondern Objekte die man dann in weiteren Schritten filtern kann, oder einzelne Informationen/Attribute abfragen kann.
|
|
shiro
Supporter
Anmeldungsdatum: 20. Juli 2020
Beiträge: 1303
|
Offenbar geht es darum ein geschwätziges Programm mit möglichst vielen Befehlszeilen zu erstellen. Daher möchte ich hier noch das gute, alte "awk" mit in den Ring werfen:
$ fileType="${1:-flac}"
$ find Ablage/ -type f -iname "*.$fileType" | sort |
awk -v fileType="$fileType" '{
f=$0
print f
# Ermittle die Verzeichnisse und die Anzahl der Files
match(f,/(.*)\/.*/,arr)
files[arr[1]]+=1
# Definition des Befehls um vol_detect und das var-Array zu füllen
cmd="ffmpeg -hide_banner -nostdin -i \""f"\" -af volumedetect -f null /dev/null 2>&1 >/dev/null | grep \"Parsed_volumedetect_0\""
#print ">"cmd"<"
vol_detect=""
delete arr
while ( ( cmd | getline zeile ) >0 ) {
#print "--" zeile
vol_detect = vol_detect zeile RS
if(match(zeile,/.*(mean|max)_volume: ([^ ]+) /,arr) >0){
var[arr[1]]=arr[2]
}
}
print "mean_volume: " var["mean"] " dB max_volume: " var["max"] " dB"
#print vol_detect
close(cmd)
} END {
for(i in files){
print "files: " files[i] " "i
}
print "Number of dirs with " fileType " files: " length(files)
}'
In "awk" kann man auch rechnen womit sich der "bc" Einsatz wahrscheinlich erübrigt.
|
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3351
|
shiro schrieb: Offenbar geht es darum ein geschwätziges Programm mit möglichst vielen Befehlszeilen zu erstellen. Daher möchte ich hier noch das gute, alte "awk" mit in den Ring werfen:
Ja das ist auch sehr schön und erstaunlich kompakt. Aber leider habe ich mich inzwischen mit Python angefreundet. In "awk" kann man auch rechnen womit sich der "bc" Einsatz wahrscheinlich erübrigt.
👍
|
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3351
|
Marc_BlackJack_Rintsch schrieb: [a, *(b, c), d] ist letztlich das gleiche wie [a, b, c, d], ich habe das bei Optionen mit zusätzlichen Wert benutzt, um das deutlicher zu machen, dass die beiden Elemente zusammengehören. ...
Der * in einer literalen Liste, gefolgt von einem iterierbaren Wert, ”entpackt” die Elemente als würden sie an der Stelle in der Liste stehen.
Danke, wieder was gelernt.
Zwei re.search() sind zumindest nicht performanter als nur einmal die Ausgabe zu durchsuchen.
Dürfte ungefähr ähnlich sein, denn im letzteren Fall mus ja gleichzeitig nach 2 Mustern gesucht werden, was sicher in Summe dann genauso aufwändig ist. Hinzu kommt allerdings das Verpacken in eine Map und das anschließende wieder Heraussuchen. Wenn man dann noch das 2. Muster erst ab der Position sucht, wo das erste gefunden wurde, beschleunigt das die Sache sicher nochmal merklich. Ich fand das eigentlich recht einfach nach beiden zu suchen. Es funktioniert auch unabhängig von der Reihenfolge in der das in der Ausgabe auftaucht.
Sehr schön, und in Anbetracht der CPU-Zeit, die ffmpeg für die Analyse braucht, ist die RegEx-Zeit hier auch ziemlich unwichtig. Allerdings bräuchte die sequenzielle Suche mit direkter Zuweisung an die Variable weniger Code-Zeilen.
Man kann den CalledProcessError behandeln und beispielsweise den Dateipfad und den Rückgabecode von ffmpeg in einer Liste speichern und die am Ende ausgeben (falls sie Inhalt hat). Das Ausnahme-Objekt hat auch die Ausgabe die ffmpeg gemacht hat, falls man die auch aufheben und später ausgeben möchte.
Wieder ganz herzlichen Dank für die Vorarbeit. Nun liefert das Programm mir genau die Berechnungen und Ausgaben, wie ich sie haben wollte, in 2 Varianten (mit / ohne --verbose): 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88 | #!/usr/bin/env python3
import re
import subprocess
import sys
from pathlib import Path
def detect_volumes(flac_file_path):
result = subprocess.run(
[
"ffmpeg",
"-hide_banner",
"-nostdin",
*("-i", str(flac_file_path)),
*("-af", "volumedetect"),
*("-f", "null"),
"/dev/null",
],
capture_output=True,
check=True,
)
volumes = {
match[1]: float(match[2])
for match in re.finditer(rb"(mean|max)_volume: (\S*)", result.stderr)
}
return (volumes[b"mean"], volumes[b"max"])
def main():
if len(sys.argv) <= 1:
print("Usage: ./vol_dedect.py START_DIR [--vervose]")
sys.exit(1)
start_dir = sys.argv[1]
verbose = sys.argv[2] if len(sys.argv) > 2 else ""
flac_directory_count = 0
flac_file_paths_with_errors = []
directory_paths = sorted(
path for path in Path(start_dir).rglob("*") if path.is_dir()
)
for directory_path in directory_paths :
num_files = 0
mean_sum = 0.0
min_max = 0.0
max_max = -64.0
flac_file_paths = sorted(
# directory_path.glob("*.[fF][lL][aA][cC]") # before Python 3.12
directory_path.glob("*.flac", case_sensitive=False)
)
for flac_file_path in flac_file_paths :
if verbose == "--verbose" :
print(flac_file_path)
try:
mean_volume, max_volume = detect_volumes(flac_file_path)
if verbose == "--verbose" :
print("mean volume: % 5.1f dB, max volume: % 5.1f dB"
%(mean_volume, max_volume))
mean_sum += mean_volume
min_max = min(min_max, max_volume)
max_max = max(max_max, max_volume)
# num_files++ # Warum geht das nicht?
num_files += 1
except subprocess.CalledProcessError as error:
flac_file_paths_with_errors.append(
(flac_file_path, error.returncode)
)
if flac_file_paths :
flac_directory_count += 1
print(len(flac_file_paths), "files,", len(flac_file_paths) - num_files,
"errors in:", directory_path)
if num_files :
print("volumes: average mean | min max | max max : % 5.1f | % 5.1f | % 5.1f dB"
%(round(mean_sum / num_files, 1), min_max, max_max))
if verbose == "--verbose" :
print()
print("\n")
print("Number of albums total: ", len(directory_paths))
print("Number of albums with flac files:", flac_directory_count)
if flac_file_paths_with_errors :
print(f"Flac files with errors ({len(flac_file_paths_with_errors)}):")
for flac_file_path, returncode in flac_file_paths_with_errors:
print(flac_file_path, "==> ffmpeg return code:", returncode)
if __name__ == "__main__" :
main()
|
Was schade ist, dass es in Python wohl keinen keinen Iterationsoperator gibt, vor allem keinen, der erst nach dem Zugriff hoch/runter zählt.
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4735
|
Die Zeit die das auswerten der Rückgabe im Vergleich zum externen Verarbeiten der Audiodaten braucht ist sicher zu vernachlässigen. Aber zweimal die gesamte Ausgabe absuchen vs. nur einmal die gesamte Ausgabe absuchen ist weniger Arbeit. Es ist ja nicht so, dass so eine reguläre Suche nach allen Varianten die man aus dem Muster bilden kann einzeln sucht. Ich denke so ein micromanaging sollte man nur machen wenn man wirklich die Zeit dafür hat, dass dann nachzumessen. Vielleicht ist es ja sogar schneller wenn man gar keine regulären Ausdrücke verwendet, aber dann wird es auch umständlicher zu beschreiben und man fängt an mit Indexwerten zu hantieren, und kann da dann auch wieder leichter „off by one“ Fehler machen. Einen ++-Operator gibt es einfach nicht. War das mit „Iterationsoperator“ gemeint? Ich hätte das eher „Inkrement-Operator“ genannt. Iterationsoperatoren sind für mich iter() und next(). Das verbose als Zeichenkette finde ich ein bisschen unschön. Das ist ja eigentlich ein Flag. Ich würde mit in schauen ob --verbose in den Argumenten ist. Das ist dann ein Wahrheitswert. round() ist eigentlich zum weiterrechnen mit einem gerundeten Wert gedacht. Das macht auch etwas das manche Leute überraschend finden, die denken „runden“ sei etwas das jeder gleich macht. Die Ausgabe wird mit einer Nachkommastelle formatiert, da ist runden auch schon enthalten.
Den %-Operator für Zeichenkettenformatierung würde man eher nicht mehr verwenden. Es gibt ja f-Zeichenketten oder falls man die ”Vorlage” nicht fest im Code hat, gibt es die format()-Methode auf Zeichenketten. Spasseshalber mal einen namedtuple()-Datentyp als Rückgabewert von detect_volume() und einen Volumes-Typ auf den man diese Werte addieren kann. Wie immer ungetestet:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116 | #!/usr/bin/env python3
import re
import subprocess
import sys
from pathlib import Path
from collections import namedtuple
Volume = namedtuple("Volume", "mean max")
class Volumes:
def __init__(self, mean_sum=0, min_max=0, max_max=-64, count=0):
self.mean_sum = mean_sum
self.min_max = min_max
self.max_max = max_max
self.count = count
def __str__(self):
return (
f"volumes: average mean | min max | max max :"
f" {self.average_mean: 5.1f} | {self.min_max: 5.1f} |"
f" {self.max_max: 5.1f} dB"
)
def __add__(self, other):
if not isinstance(other, Volume):
raise TypeError(f"can not add {type(self)} and {type(other)}")
return Volumes(
self.mean_sum + other.mean,
min(self.min_max, other.max),
max(self.max_max, other.max),
self.count + 1,
)
@property
def average_mean(self):
return self.mean_sum / self.count
def detect_volume(flac_file_path):
result = subprocess.run(
[
"ffmpeg",
"-hide_banner",
"-nostdin",
*("-i", str(flac_file_path)),
*("-af", "volumedetect"),
*("-f", "null"),
"/dev/null",
],
capture_output=True,
check=True,
)
return Volume(
float(re.search(rb"mean_volume: (\S*)", result.stderr)[1]),
float(re.search(rb"max_volume: (\S*)", result.stderr)[1]),
)
def main():
verbose = "--verbose" in sys.argv[1:]
flac_directory_count = 0
flac_file_paths_with_errors = []
directory_paths = sorted(
path for path in Path(".").rglob("*") if path.is_dir()
)
for directory_path in directory_paths:
flac_file_paths = sorted(
# directory_path.glob("*.[fF][lL][aA][cC]") # before Python 3.12
directory_path.glob("*.flac", case_sensitive=False)
)
volumes = Volumes()
for flac_file_path in flac_file_paths:
if verbose:
print(flac_file_path)
try:
volume = detect_volume(flac_file_path)
if verbose:
print(
f"mean volume: {volume.mean: 5.1f} dB,"
f" max volume: {volume.max: 5.1f} dB"
)
volumes += volume
except subprocess.CalledProcessError as error:
flac_file_paths_with_errors.append(
(flac_file_path, error.returncode)
)
if flac_file_paths:
flac_directory_count += 1
print(
f"{len(flac_file_paths)} files,"
f" {len(flac_file_paths) - volumes.count} errors in:"
f" {directory_path}"
)
if volumes.count:
print(volumes)
if verbose:
print()
print("Number of albums total: ", len(directory_paths))
print("Number of albums with flac files:", flac_directory_count)
print("\n")
if flac_file_paths_with_errors:
print(f"Flac files with errors ({len(flac_file_paths_with_errors)}):")
for flac_file_path, returncode in flac_file_paths_with_errors:
print(flac_file_path, "==> ffmpeg return code:", returncode)
if __name__ == "__main__":
main()
|
|
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3351
|
Marc_BlackJack_Rintsch schrieb: Es ist ja nicht so, dass so eine reguläre Suche nach allen Varianten die man aus dem Muster bilden kann einzeln sucht.
Nun ja, bei der kombinierten Suche braucht's erst mal ein compare("mean_") UND noch ein compare("max_") auf JEDER Position im Stream UND dann noch ein compare("volume"), bei der Einzelsuche jeweils nur ein compare("mean_volume") ODER ein compare("max_volume") je Suche. Jeder Suchaufruf kostet (fast) gleich viel, egal ob nach 3 oder 10 Zeichen, denn beim ersten nicht passenden Zeichen wird ja immer jeweils abgebrochen. Ein bisschen helfen konnte man dem RegExer noch mit "m(ean|ax)_volume: (\S*)". Vielleicht ist es ja sogar schneller wenn man gar keine regulären Ausdrücke verwendet, aber dann wird es auch umständlicher zu beschreiben und man fängt an mit Indexwerten zu hantieren, ...
Das bestimmt, vor allem wenn man die Suche nach dem 2. Muster erst ab dem Index beginnt, wo das erste schon gefunden wurde.
War das mit „Iterationsoperator“ gemeint? Ich hätte das eher „Inkrement-Operator“ genannt.
Ja, mea culpa.
Das verbose als Zeichenkette finde ich ein bisschen unschön. Das ist ja eigentlich ein Flag. Ich würde mit in schauen ob --verbose in den Argumenten ist. Das ist dann ein Wahrheitswert.
round() ist eigentlich zum weiterrechnen mit einem gerundeten Wert gedacht. Das macht auch etwas das manche Leute überraschend finden, die denken „runden“ sei etwas das jeder gleich macht. Die Ausgabe wird mit einer Nachkommastelle formatiert, da ist runden auch schon enthalten.
(ich wusste nicht, dass 5.1f rundet, hätte eher gedacht, dass es abschneidet) Den %-Operator für Zeichenkettenformatierung würde man eher nicht mehr verwenden.
Er liest sich aber besser, weil man dann schön das Muster für die ganze Zeile vor Augen hat. Es gibt ja f-Zeichenketten
Ja nach der Kombination von {...} mit 5.1f hatte ich gesucht und nichts gefunden 💡 oder falls man die ”Vorlage” nicht fest im Code hat, gibt es die format()-Methode auf Zeichenketten.
Diese Syntax finde ich hässlich, unübersichtlich und aufwändig.
Spasseshalber mal einen namedtuple()-Datentyp als Rückgabewert von detect_volume() und einen Volumes-Typ auf den man diese Werte addieren kann.
Uih, wenn's um die Definition von Klassen und Records in Python-Skripts geht, wird die Syntax aber echt hässlich. Das ist bei "richtigen" Programmiersprachen wie Java schöner gemacht. Danke dennoch für das Beispiel. Wie immer ungetestet:
Läuft, aber die Übergabe des Start-Verzeichnisses per sys.argv[1] fehlt noch und die "Usage"-Ausgabe, wenn sie fehlt. Danke für die Mühen. Meine aktuelle Version: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93 | #!/usr/bin/env python3
import re
import subprocess
import sys
from pathlib import Path
def detect_volumes(flac_file_path):
result = subprocess.run(
[
"ffmpeg",
"-hide_banner",
"-nostdin",
*("-i", str(flac_file_path)),
*("-af", "volumedetect"),
*("-f", "null"),
"/dev/null",
],
capture_output=True,
check=True,
)
return (
float(re.search(rb"mean_volume: (\S*)", result.stderr)[1]),
float(re.search(rb"max_volume: (\S*)", result.stderr)[1])
)
# volumes = { # use dictionary (= HashMap)
# match[1]: float(match[2])
# for match in re.finditer(rb"m(ean|ax)_volume: (\S*)", result.stderr)
# }
# return (volumes[b"ean"], volumes[b"ax"])
def main():
if len(sys.argv) <= 1:
print("Usage: ./volume-detect.py START_DIR [--verbose]")
sys.exit(1)
start_dir = sys.argv[1]
verbose = "--verbose" in sys.argv[2:]
flac_directory_count = 0
flac_file_paths_with_errors = []
directory_paths = sorted(
path for path in Path(start_dir).rglob("*") if path.is_dir()
)
for directory_path in directory_paths :
num_files = 0
mean_sum = 0.0
min_max = 0.0
max_max = -64.0
flac_file_paths = sorted(
directory_path.glob("*.flac", case_sensitive=False)
# directory_path.glob("*.[fF][lL][aA][cC]") # before Python 3.12
)
for flac_file_path in flac_file_paths :
if verbose :
print(flac_file_path)
try:
mean_volume, max_volume = detect_volumes(flac_file_path)
if verbose :
print("mean volume: % 5.1f dB, max volume: % 5.1f dB"
%(mean_volume, max_volume))
# print(f"mean volume: {mean_volume: 5.1f} dB,"
# f" max volume: {max_volume: 5.1f} dB")
mean_sum += mean_volume
min_max = min(min_max, max_volume)
max_max = max(max_max, max_volume)
num_files += 1
except subprocess.CalledProcessError as error:
flac_file_paths_with_errors.append(
(flac_file_path, error.returncode)
)
if flac_file_paths :
flac_directory_count += 1
print(len(flac_file_paths), "files,", len(flac_file_paths) - num_files,
"errors in:", directory_path)
if num_files :
print("volumes: average mean | min max | max max : % 5.1f | % 5.1f | % 5.1f dB"
%(mean_sum / num_files, min_max, max_max))
if verbose :
print()
print("\n")
print("Number of albums total: ", len(directory_paths))
print("Number of albums with flac files:", flac_directory_count)
if flac_file_paths_with_errors :
print(f"Flac files with errors ({len(flac_file_paths_with_errors)}):")
for flac_file_path, returncode in flac_file_paths_with_errors:
print(flac_file_path, "==> ffmpeg return code:", returncode)
if __name__ == "__main__" :
main()
|
|
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3351
|
@ Marc_BlackJack_Rintsch Ich habe jetzt noch ein kleines Problem. Wenn ich ein einzelnes Album nochmal mit --verbose untersuchen will, gebe ich z.B. Ablage/Album1 als Startpfad vor. Leider werden aber mit | path for path in Path(start_dir).rglob("*") if path.is_dir()
|
nur die Unterordner von start_dir gesammelt, sodass ich keine Ausgabe für Ablage/Album1 bekomme. wie kann da der Startordner selbst noch hinzugefügt werden? Folgendes z.B. funktioniert nicht: | path for path in (Path(start_dir), Path(start_dir).rglob("*")) if path.is_dir()
|
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4735
|
Wie liest sich der %-Operator besser? Man hat da die Muster und die Werte getrennt und muss hin und her schauen und abzählen welcher Wert denn am Ende bei welchem Muster landet. Es ist doch viel leichter zu sehen wenn man den Wert/Namen und die Formatierung zusammen an der Stelle hat wo beides im Text am Ende stehen wird. Und wenn man das getrennt haben möchte, dann kann man ja auch die format()-Methode verwenden. % ist nur noch da weil es leider an wenigen Stellen in der Standardbibliothek nicht einfach ersetzt werden kann, ohne das viel Code der das ausserhalb benutzt, umgeschrieben werden muss. Das hat man in Python 2 schon nicht mehr verwendet, sei dem es str.format() gab. Und was ist daran hässlicher, unübersichtlicher, und aufwändiger? Man kann das genau so verwenden wie % wenn man denn unbedingt will, hat aber mehr Möglichkeiten das verständlicher und eben nicht so unübersichtlich zu gestalten. Das ist vielleicht auch einfach eine Gewohnheitsfrage. Denn ich verstehe so gar nicht was an Klassen oder Funktionen die Datentypen erstellen hässlich sein soll. Java hat so etwas nicht. Hat keine Operatorüberladung und keine Properties. Das führt zu hässlichem Code, zum Beispiel zu lauter trivialen Gettern und Settern und ähnlichem Boilerplate. Oder der nicht so wirklich intuitiven equals()-Methode die man für Vergleiche verwenden muss statt ==. Und das man keine numerischen Typen haben kann, die wie Zahlen einfach mit den Rechenoperatoren funktionieren. Python ist da ein bisschen objektorientierter in dem wirklich alles ein Objekt ist was man an einen Namen binden kann, inklusive Funktionen, Klassen, Methoden, und Module. Und man kann Funktionen/Methoden schreiben, die das alles als Argumente bekommen können, oder auch neue Objekte von diesen Typen erstellen und als Rückgabewerte haben können. Wenn das mit Argumenten nicht wirklich sehr simpel ist, würde ich das argparse-Modul verwenden. Das braucht hier für minimale Funktion auch nicht viel mehr, man bekommt aber „Usage“ und Hilfe gratis dazu und die Reihenfolge von Argumenten und Optionen ist egal, man kann also auch erst --verbose und dann das Startverzeichnis angeben. Wenn man es einfach ohne Argumente aufruft:
$ ./forum37.py
usage: forum37.py [-h] [--verbose] START_DIR
forum37.py: error: the following arguments are required: START_DIR
Wenn man die Option für die Hilfe angibt:
$ ./forum37.py -h
usage: forum37.py [-h] [--verbose] START_DIR
positional arguments:
START_DIR start here to search for directories with FLAC files
options:
-h, --help show this help message and exit
--verbose print more information while processing the directory tree Wenn man ein Tupel mit zwei Elementen erstellt und da drüber iteriert, dann sind das natürlich nur zwei Schleifendurchläufe und das zweite Element ist kein Pfad sondern ein Objekt das beim iterieren Pfade liefert. Man kann mehrere iterierbare Objekte mit itertools.chain() verketten. Also den einzelnen Pfad in einer Liste und das rglob()-Ergebnis verketten. Wenn keine Klasse(n) dann vielleicht etwas aus dem inneren der Verschachtelten Schleifen hereus ziehen um die vielen Variablen in einem Namensraum zu beseitigen. ☺ Wie immer ungetestet:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114 | #!/usr/bin/env python3
import re
import subprocess
from argparse import ArgumentParser
from itertools import chain
from pathlib import Path
def detect_volumes(flac_file_path):
result = subprocess.run(
[
"ffmpeg",
"-hide_banner",
"-nostdin",
*("-i", str(flac_file_path)),
*("-af", "volumedetect"),
*("-f", "null"),
"/dev/null",
],
capture_output=True,
check=True,
)
return (
float(re.search(rb"mean_volume: (\S*)", result.stderr)[1]),
float(re.search(rb"max_volume: (\S*)", result.stderr)[1]),
)
def process_flac_files(directory_path, flac_file_paths, verbose):
file_count = 0
mean_sum = 0.0
min_max = 0.0
max_max = -64.0
for flac_file_path in flac_file_paths:
if verbose:
print(flac_file_path)
try:
mean_volume, max_volume = detect_volumes(flac_file_path)
except subprocess.CalledProcessError as error:
yield (flac_file_path, error.returncode)
else:
if verbose:
print(
"mean volume: % 5.1f dB, max volume: % 5.1f dB"
% (mean_volume, max_volume)
)
mean_sum += mean_volume
min_max = min(min_max, max_volume)
max_max = max(max_max, max_volume)
file_count += 1
print(
len(flac_file_paths),
"files,",
len(flac_file_paths) - file_count,
"errors in:",
directory_path,
)
if file_count:
print(
"volumes: average mean | min max | max max : % 5.1f | % 5.1f | % 5.1f dB"
% (mean_sum / file_count, min_max, max_max)
)
if verbose:
print()
def main():
parser = ArgumentParser()
parser.add_argument(
"--verbose",
action="store_true",
help="print more information while processing the directory tree",
)
parser.add_argument(
"start_dir",
metavar="START_DIR",
help="start here to search for directories with FLAC files",
)
args = parser.parse_args()
start_path = Path(args.start_dir)
flac_directory_count = 0
flac_file_paths_with_errors = []
directory_paths = sorted(
path
for path in chain([start_path], start_path.rglob("*"))
if path.is_dir()
)
for directory_path in directory_paths:
flac_file_paths = sorted(
directory_path.glob("*.flac", case_sensitive=False)
)
if flac_file_paths:
flac_directory_count += 1
flac_file_paths_with_errors.extend(
process_flac_files(
directory_path, flac_file_paths, args.verbose
)
)
print("\n")
print("Number of albums total: ", len(directory_paths))
print("Number of albums with flac files:", flac_directory_count)
if flac_file_paths_with_errors:
print(f"Flac files with errors ({len(flac_file_paths_with_errors)}):")
for flac_file_path, returncode in flac_file_paths_with_errors:
print(flac_file_path, "==> ffmpeg return code:", returncode)
if __name__ == "__main__":
main()
|
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4735
|
@shiro: Eine nahezu 1:1 Übersetzung von Deinem Shell/AWK-Programm ist in Python genau so kompakt. Mit dem Vorteil, dass in den Dateipfaden alle in Dateipfaden erlaubten Zeichen vorkommen dürfen. Insbesondere auch Anführungszeichen und Dollarzeichen. Ungetestet:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | #!/usr/bin/env python3
import collections, pathlib, re, subprocess, sys
volume_re = re.compile(rb"Parsed_volumedetect_0.*(mean|max)_volume: ([^ ]+) ")
file_type = sys.argv[1] if len(sys.argv) > 1 else "flac"
path_to_file_count = collections.defaultdict(int)
for file_path in sorted(pathlib.Path("Ablage").rglob("*." + file_type)):
print(file_path)
path_to_file_count[file_path.parent] += 1
command = [*"ffmpeg -hide_banner -nostdin -i".split(), str(file_path), *"-af volumedetect -f null /dev/null".split()]
volume = {}
with subprocess.Popen(command, stderr=subprocess.PIPE) as process:
for line in process.stderr:
if match := re.search(volume_re, line):
volume[match[1]] = float(match[2])
print(f"mean_volume: {volume[b'mean']} dB max_volume: {volume[b'max']} dB")
for path, count in path_to_file_count.items():
print("files:", count, path)
print("Number of dirs with", file_type, "files:", len(path_to_file_count))
|
|
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3351
|
Marc_BlackJack_Rintsch schrieb: Das ist vielleicht auch einfach eine Gewohnheitsfrage. Denn ich verstehe so gar nicht was an Klassen oder Funktionen die Datentypen erstellen hässlich sein soll.
Mir gefällt vor allem nicht, dass man x-fach self explizit und als Parameter hinschreiben muss, wo das doch meistens redundant ist und die Unterstrich-Notation. Ansonsten alles fein.
Java hat ... keine Operatorüberladung und keine Properties.
Das stimmt, und das ist schade.
Schade, dass man in Python bei Arrays / iterierbaren Objekten nicht an Operatorüberladung gedacht hat. Wäre doch schick, wenn man sie mit "+" einfach verketten könnte, so wie in Java Strings. Auch VarArgs für sorted() könnten das iterieren über mehrere Objekte vereinfachen.
Python ist da ein bisschen objektorientierter ...
Gibt es da denn auch Vererbung, Interfaces und hierarchische Packages für separate Namensräume wie in Java?
Wenn das mit Argumenten nicht wirklich sehr simpel ist, würde ich das argparse-Modul verwenden.
Ja sehr schick. Da ich aber kein Produkt für die Welt herstellen will, sondern für die Analyse meiner FLAC-Dateien nur einmalig "mal eben ein Skript" brauche, reicht mir hier mein simpler Ansatz.
Wenn keine Klasse(n) dann vielleicht etwas aus dem inneren der Verschachtelten Schleifen hereus ziehen um die vielen Variablen in einem Namensraum zu beseitigen. ☺
Ja sehr schick, aber ich lasse es jetzt mal mit Deinem dankeswerten Tipp für die Verkettung mit dem Stammverzeichnis.
|
|
snafu1
Anmeldungsdatum: 5. September 2007
Beiträge: 2136
|
UlfZibis schrieb: Schade, dass man in Python bei Arrays / iterierbaren Objekten nicht an Operatorüberladung gedacht hat. Wäre doch schick, wenn man sie mit "+" einfach verketten könnte, so wie in Java Strings.
Kann man doch: Strings sind iterierbar und können verkettet werden: | >>> "abc" + "def"
'abcdef'
|
"Arrays" (ich schätze du meinst Listen) genau so: | >>> [1, 2, 3] + [4, 5, 6]
[1, 2, 3, 4, 5, 6]
|
Und das funktioniert noch mit vielen weiteren Objekten in Python.
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4735
|
@UlfZibis: Ich verstehe die Kritik am self nicht wirklich. Das ist ein Argument das eben erwartet wird, das ist nicht wirklich redundant. Finde ich besser als ein mysteriöses this das einfach aus dem Nichts auftaucht. Es macht auch die Implementierung der Sprache einfacher. Auf einer Klasse ist das Argument tatsächlich auch benutzbar und notwendig, weil das auf der Klasse noch die originale Funktion/ungebundene Methode ist. Und man kann Klassen wie gesagt auch zur Laufzeit erstellen und mit Methoden versorgen, die zu dem Zeitpunkt noch Funktionen sind. Dann müsste ja jede Funktion ein implizites this kennen‽ Was würde das bei normalen Funktionen bedeuten/für einen Wert haben? Was bei Klassenmethoden? self ist schnell getippt, wird in den simpelsten aktuellen Editoren nach dem ersten mal in der Autovervollständigung auftauchen, und falls das immer noch nicht reicht, können viele Editoren so etwas wie „snippets“ wo man sich eine Vorlage für Methoden hinterlegen kann. Falls es das nicht schon fertig gibt.
Hier mal verschiedene Arten die gleiche Klasse zu erstellen:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | class Class:
def method(self, argument):
...
# ist äquivalent zu
def function(self, argument):
...
class Class:
method = function
# bzw.
class Class:
...
Class.method = function
# bzw.
# Das zweite Argument ist ein Tupel mit den Basisklassen von denen geerbt werden
# soll. `object` ist implizit.
Class = type("Class", (), {"method": function})
|
Wenn man ein Exemplar mit dieser Klasse erstellt, haben die Methoden ein Argument weniger, weil das erste Argument von (normalen) Methoden an das Objekt gebunden wird.
| instance = Class()
instance.method(42) # Hier steht das erste Argument vor dem `.`
# Über die Klasse aufgerufen, dazu muss man die kennen.
Class.method(instance, 42)
# Der allgemeine Fall — das was hinter den Kulissen beim normalen Aufruf passiert.
type(instance).method(instance, 53)
|
Beispiele wo kein self als erstes Argument steht:
1
2
3
4
5
6
7
8
9
10
11
12 | class Class:
...
@classmethod
def foo(cls, argument):
# Bekommt die Klasse als erstes Argument. Für alternative Wege ein
# Exemplar zu erstellen.
@staticmethod
def function(argument):
# Einfach eine Funktion die zumindest technisch nichts mit einem
# Exemplar oder der Klasse zu tun hat.
|
Die einrahmenden Unterstriche sind eine einfache Möglichkeit einen ”Namensraum” für Implementierungsdetails zu haben, so dass man sofort sieht, da wird Verhalten im Zusammenspiel der mit der Sprache implementiert, und ohne dass man diese Namen alle kennen muss um Kollisionen mit eigenen Methoden-/Attributnamen zu vermeiden. Hätte man durch andere ”Sonderzeichen” oder zusätzliche Syntax erreichen können, aber so ist das einfach und braucht nicht wirklich zwingend extra Code in der Sprachimplementierung. Da finde ich JavaScript deutlich unschöner. def __iter__(self): yield 42 vs. *[Symbol.iterator]() { yield 42; }. Sequenztypen, also auch Listen und Tupel, haben + zum verketten überladen. Auch bytearray und bytes. Unter Arrays versteht man ansonsten in der Regel das was die externe Numpy-Bibliothek als Array-Objekte zur Verfügung stellt, und da ist + auch überladen, aber bedeutet die komponentenweise Addition.
1
2
3
4
5
6
7
8
9
10
11
12 | >>> [1, 2, 3] + [10, 20, 30]
[1, 2, 3, 10, 20, 30]
>>> bytearray([1, 2, 3]) + bytearray([253, 254, 255])
bytearray(b'\x01\x02\x03\xfd\xfe\xff')
>>> import numpy as np
>>> np.array([2, 1, 0]) + 1
array([3, 2, 1])
>>> np.array([2, 1, 0]) + 1 + np.array([10, 20, 30])
array([13, 22, 31])
|
Allgemein iterierbare Objekte ist halt schwierig insbesondere wenn man die mischen möchte. Da ist itertools.chain() viel flexibler, weil das aus wirklich beliebig mischbaren, iterierbaren Objekten, einen Iterator macht, der nacheinander alle Elemente liefert. Man könnte vielleicht + bei Iteratoren und Generatoren implementieren, aber das muss dann auch wirklich jeder Iteratortyp machen, und im Grunde auch jedes mal auf die gleiche Art, also viel wiederholter Code. chain() funktioniert ohne das. Und iter(a) + iter(b) finde ich jetzt auch nicht wirklich besser als chain(a, b). Es gibt nicht nur Vererbung, sondern sogar Mehrfachvererbung. Interfaces kann man sich aus normalen Klassen basteln, muss man aber nicht, sofern sie keine Funktionalität enthalten. (Gibt es mittlerweile in Java.) In Python findet ja keine Typprüfung statt. Falls man Typannotationen verwenden will, dann gibt es typing.Protocol, das als Basisklasse für so etwas wie Interfaces dient. VarArgs für sorted()? Da stehe ich gerade auf dem Schlauch? Meinst Du sorted(a, b, c) statt sorted(chain(a, b, c))? Packages sind Verzeichnisse mit Modulen von denen das Modul mit dem Namen __init__.py den Inhalt des Packages definiert wenn man es als Modul importiert. Die Hierarchie kommt durch die Verzeichnisse und jedes Modul ist ein Namensraum.
foolib/
sub/
__init__.py
sub_bar.py
__init__.py
bar.py
Wenn alle *.py-Dateien eine Klasse Foo definieren, dann gibt es foolib.Foo, foolib.bar.Foo, foolib.sub.Foo, und foolib.sub.sub_bar.Foo. Alle in einem eigenen Namensraum. Man kann das auch einfach Schrittweise verfeinern. Wenn man mit einem Modul foolib.py anfängt und man merkt, dass das sinnvoll aufgeteilt werden könnte kann man im ersten Schritt ein Verzeichnis foolib/ erstellen und das Modul als __init__.py dort rein verschieben, ohne das sich etwas geändert hat wie man das importiert und benutzt. Und dann kann man in foolib/ weitere Module anlegen die unter diesen Namensraum fallen. Beliebter Anfängerfehler bei Java-Programmierern ist das eine eine Datei pro Klasse-Muster. Damit wird das Modul völlig ignoriert, welches dazu da ist zusammengehörende Funktionen und Klassen zusammenzufassen. Packages fassen dann zusammengehörende Module zusammen. So etwas wie ein Modul kennt Java nicht. Das mit argparse ist nicht wirklich länger als Dein Code das manuell zu machen wenn man auf die Hilfetexte verzichtet. Sieht man im Code unten. Ich habe mal die Portierung von shiro's AWK nach Python um die Auswertung ergänzt. Was das nicht leisten kann, ist die Verzeichnisse zählen in denen keine FLAC-Dateien vorkommen, denn diese Information wird im allerersten Schritt schon nicht erhoben, wo alle *.flac-Dateien rekursiv gesucht werden. Die gruppiere ich dann nach Verzeichnis in dem sie liegen, und dann kommt die schon bekannte Auswertung auf diesen Gruppen. 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99 | #!/usr/bin/env python3
import re
import subprocess
from argparse import ArgumentParser
from collections import namedtuple
from itertools import groupby
from pathlib import Path
VOLUME_RE = re.compile(rb"Parsed_volumedetect_0.*(mean|max)_volume: ([^ ]+) ")
Volume = namedtuple("Volume", ["mean", "max"])
def detect_volume(file_path):
result = subprocess.run(
[
"ffmpeg",
"-hide_banner",
"-nostdin",
*("-i", str(file_path)),
*("-af", "volumedetect"),
*("-f", "null"),
"/dev/null",
],
capture_output=True,
check=True,
)
return Volume(
**{
match[1].deocde("ascii"): float(match[2])
for match in VOLUME_RE.finditer(result.stderr)
}
)
def main():
parser = ArgumentParser()
parser.add_argument("--verbose", action="store_true")
parser.add_argument("--file-type", metavar="TYPE", default="flac")
parser.add_argument("start_dir", metavar="START_DIR")
args = parser.parse_args()
all_file_paths = sorted(
Path(args.start_dir).rglob("*." + args.file_type, case_sensitive=False)
)
file_paths_with_errors = []
dir_count = 0
for dir_count, (dir_path, file_paths) in enumerate(
groupby(all_file_paths, key=lambda path: path.parent), 1
):
file_paths = list(file_paths)
mean_sum = 0
min_max = 0
max_max = -64
file_count = 0
for file_path in file_paths:
if args.verbose:
print(file_path)
try:
volume = detect_volume(file_path)
except subprocess.CalledProcessError as error:
file_paths_with_errors.append((file_path, error.returncode))
else:
if args.verbose:
print(
f"mean_volume: {volume.mean} dB,"
f" max_volume: {volume.max} dB"
)
mean_sum += volume.mean
min_max = min(min_max, volume.max)
max_max = max(max_max, volume.max)
file_count += 1
print(
f"{len(file_paths)} files, {len(file_paths) - file_count} errors"
f" in: {dir_path}"
)
if file_count:
print(
f"volumes: average mean | min max | max max :"
f" {mean_sum / file_count: 5.1f}"
f" | {min_max: 5.1f}"
f" | {max_max: 5.1f} dB"
)
if args.verbose:
print()
print("\n")
print("Number of dirs with media files:", dir_count)
if file_paths_with_errors:
print(f"Media files with errors ({len(file_paths_with_errors)}):")
for file_path, returncode in file_paths_with_errors:
print(file_path, "==> ffmpeg return code:", returncode)
if __name__ == "__main__":
main()
|
|
|
UlfZibis
(Themenstarter)
Anmeldungsdatum: 13. Juli 2011
Beiträge: 3351
|
snafu1 schrieb: "Arrays" (ich schätze du meinst Listen)
Ja, richtig. | >>> [1, 2, 3] + [4, 5, 6]
[1, 2, 3, 4, 5, 6]
|
Sehr schön, geht nur hier leider nicht: | path for path in ([start_dir] + Path(start_dir).rglob("*"))
|
TypeError: can only concatenate list (not "generator") to list
Eigentlich fehlt nur eine Erweiterung von rglob(): | Path(start_dir).rglob("*", true) # Inkludiere das Basisverzeichnis
|
Aber an anderer Stelle geht die Verkettung, womit chain() obsolet wird: | for directory_path in [start_path] + directory_paths :
|
oder besser (damit die Statistik am Ende stimmt): | directory_paths = [start_path] + sorted(
path for path in start_path.rglob("*") if path.is_dir()
)
|
💡 Meine aktuelle Version: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87 | #!/usr/bin/env python3
import re
import subprocess
import sys
from pathlib import Path
def detect_volumes(flac_file_path):
result = subprocess.run(
["ffmpeg", "-hide_banner", "-nostdin",
*("-i", str(flac_file_path)),
*("-af", "volumedetect"), *("-f", "null"), "/dev/null"],
capture_output=True,
check=True
)
return (
float(re.search(rb"mean_volume: (\S*)", result.stderr)[1]),
float(re.search(rb"max_volume: (\S*)", result.stderr)[1])
)
# volumes = { # use dictionary (= HashMap)
# match[1]: float(match[2])
# for match in re.finditer(rb"m(ean|ax)_volume: (\S*)", result.stderr)
# }
# return (volumes[b"ean"], volumes[b"ax"])
def main():
if len(sys.argv) <= 1:
print("Usage: ./volume-detect.py START_DIR [--verbose]")
sys.exit(1)
start_dir = Path(sys.argv[1])
verbose = "--verbose" in sys.argv[2:]
flac_directory_count = 0
flac_paths_with_errors = []
directory_paths = [start_dir] + sorted(
path for path in start_dir.rglob("*") if path.is_dir()
)
for directory_path in directory_paths :
num_files = 0
mean_sum = 0.0
min_max = 0.0
max_max = -64.0
flac_file_paths = sorted(
directory_path.glob("*.flac", case_sensitive=False)
# directory_path.glob("*.[fF][lL][aA][cC]") # before Python 3.12
)
for flac_file_path in flac_file_paths :
if verbose :
print(flac_file_path)
try:
mean_volume, max_volume = detect_volumes(flac_file_path)
if verbose :
print("mean volume: % 5.1f dB, max volume: % 5.1f dB"
%(mean_volume, max_volume))
# print(f"mean volume: {mean_volume: 5.1f} dB,"
# f" max volume: {max_volume: 5.1f} dB")
mean_sum += mean_volume
min_max = min(min_max, max_volume)
max_max = max(max_max, max_volume)
num_files += 1
except subprocess.CalledProcessError as error:
flac_paths_with_errors.append(
(flac_file_path, error.returncode)
)
if flac_file_paths :
flac_directory_count += 1
print(len(flac_file_paths), "files,", len(flac_file_paths) - num_files,
"errors in:", directory_path)
if num_files :
print("volumes: average mean | min max | max max : % 5.1f | % 5.1f | % 5.1f dB"
%(mean_sum / num_files, min_max, max_max))
if verbose :
print()
print()
print("Albums total: ", len(directory_paths))
print("Albums with flac files:", flac_directory_count)
if flac_paths_with_errors :
print(f"Flac files with errors ({len(flac_paths_with_errors)}):")
for flac_file_path, returncode in flac_paths_with_errors:
print(flac_file_path, "==> ffmpeg return code:", returncode)
if __name__ == "__main__" :
main()
|
|
|
Marc_BlackJack_Rintsch
Ehemalige
Anmeldungsdatum: 16. Juni 2006
Beiträge: 4735
|
@UlfZibis: Falls das mal kommt, wäre es sicher ein „keyword only“-Argument. Dann braucht man auch den Kommentar nicht:
| path.rglob("*", include_self=True)
|
Allerdings wäre das wahrscheinlich einen Tick mehr als nur ein return chain([self], other_paths), denn es würde wenn es wie bei find funktionieren soll, auch self noch gegen das „glob pattern“ prüfen müssen. Bei find sieht das ja beispielsweise so aus:
$ find Ablage -name '*'
Ablage
Ablage/Interpret
Ablage/Interpret/Album3
Ablage/Interpret/Album3/123.jpg
Ablage/Interpret/Album3/01 titel1.mp3
Ablage/Interpret/Album3/02 titel2.mp3
Ablage/Interpret/Album2
Ablage/Interpret/Album2/01 titel1.flac
Ablage/Interpret/Album2/02 titel2.flac
Ablage/Interpret/Album2/123.jpg
Ablage/Album4
Ablage/Album1
Ablage/Album1/02-titel2.flac
Ablage/Album1/123.jpg
Ablage/Album1/01-titel1.flac
$ find Ablage -name 'Al*'
Ablage/Interpret/Album3
Ablage/Interpret/Album2
Ablage/Album4
Ablage/Album1
Kannst ja ein Ticket bei Python aufmachen ob die eventuell so etwas einbauen, mit find als ”Präzedenzfall”. Insbesondere das da auch nach dem Muster gefiltert wird, verhindert das man da einfach „Nimm doch chain()“ sagen kann. Was soll denn [start_dir] + Path(start_dir).rglob("*") bedeuten? Sollte da a) das erste Argument in einen Iterator umgewandelt werden der mit dem zweiten Argument verkettet wird? Oder b) eine Liste mit den verketteten Inhalten der beiden iterierbaren Objekte erstellt werden? Und was ist dann mit Numpy-Arrays? Die sind iterierbar aber + ist anders überladen und funktioniert nur weil + auf Listen nichts anderes als Listen kann. Ich sehe nicht so ganz wie man das lösen sollte. Denn folgendes darf dabei nicht kaputt gehen, weil es zu viel Code da draussen gibt, der das so verwendet (np = Numpy):
| >>> a = [1, 2, 3]
>>> b = np.array([23, 42, 4711])
>>> a + b
array([ 24, 44, 4714])
>>> b + a
array([ 24, 44, 4714])
|
Und beide Varianten kann man ja auch ohne überladenes + lösen:
| # a) Iterator über alles.
paths = chain([start_dir], Path(start_dir).rglob("*"))
# b) Liste mit allem.
paths = [start_dir, *Path(start_dir).rglob("*")]
|
Variante a) steht auch bei den ”Rezepten” in der itertools-Dokumentation. Falls man sich das nicht selbst als Funktion implementieren möchte: das externe more_itertools hat (unter anderem) die ”Rezepte” alle implementiert. Damit könnte man das also auch als prepend(start_dir, Path(start_dir).rglob("*")) schreiben. Edit: Ich sehe nicht was an dem + so toll sein soll das man auf chain() verzichtet. Mich stört da der Speicherverbrauch, wenn auch wahrscheinlich egal bei heutigen Rechnern. liste_a + liste_b erzeugt eine neue Liste mit allen Elementen aus beiden Listen. chain(liste_a, liste_b) hat dagegen konstanten Speicherverbrauch, egal wie gross die Listen sind, weil sich das chain-Objekt nur zwei Zahlen merken muss: in welcher Liste bin ich gerade und dort an welchem Index. Edit2: Wenn's das nicht schon gäbe, könnte man das so selbst schreiben:
| def chain(*iterables):
for iterable in iterables:
yield from iterable
|
|