# [SQL, Python] Eigenes Programm zur Verwaltung von Musik



## LastChaosTyp (21. Juli 2016)

Hallo PCGHler,

ich habe mich jetzt mal an ein Projekt gewagt, welches ein eigener "MP3-Player" werden soll. Im Moment beschäftige ich mich noch nicht mit dem Interface etc. sondern erstmal mit den interen Basics wie z.B. das Eintragen aller MP3 Dateien in eine Datenbank mit Künseltername, Länge, ... Das klappt soweit ganz gut, jedoch bin ich jetzt auf ein Problem mit der SQL-Syntax gestoßen :/ Erstmal hier der Code, ich weiß, dass man den noch an vielen Stellen optimieren kann, aber Optimierungen mach ich erst am Ende 


```
# -------------------------- Imports --------------------------

import os
import sys
import sqlite3
import pyglet
from mutagen.mp3 import MP3
from mutagen.easyid3 import EasyID3

# -------------------------- Klassen --------------------------

class Database(object):
    def __init__(self, path, name):
        self.path = path
        self.name = name
        self.cursor = None
        self.connection = None

    def connect(self):
        self.connection = sqlite3.connect(self.path)
        self.cursor = self.connection.cursor()
        print("Verbindung zur Datenbank wurde erfolgreich hergestellt!")

    def disconnect(self):
        self.connection.commit()
        self.connection.close()
        print("Verbindung zur Datenbank wurde erfolgreich geschlossen!")

    def execute(self, code):
        self.cursor.execute(code)
        self.connection.commit()

# -------------------------- Methoden --------------------------

def checkPath(path):
    if not os.path.exists(path):
        os.makedirs(path)
    
def createDatabase(path, name):
    return Database(path, name)

def createTable(database):    
    database.execute("CREATE TABLE songs(song_index INTEGER PRIMARY KEY ASC, path TEXT(500) NOT NULL, title TEXT(200) NOT NULL, artist TEXT(200), duration INTEGER NOT NULL, counter INT DEFAULT 0);")

def checkTable(database):
    database.execute("SELECT name FROM sqlite_master WHERE type='table';")
    result = database.cursor.fetchall()
    if result == []:
        createTable(database)
        print("Die Tabelle wurde erfolgreich angelegt!")

def titleExistsInDatabase(database, path):
    database.execute("SELECT title FROM songs WHERE path='" + path + "';")
    result = database.cursor.fetchall()
    if(result != []):
        return True
    else:
        return False
    
def addTitleToDatabase(database, path, title, artist, duration):
    if not titleExistsInDatabase (database, path):
        database.execute("INSERT INTO songs (song_index, path, title, artist, duration, counter) VALUES (NULL, '" + path + "', '" + title + "', '" + artist + "', " + str(duration) + ", 0);")
        print("Der Musiktitel '" + title + "' wurde erfolgreich zur Datenbank hinzugefügt!")
        return True
    else:
        print("Dieser Musiktitel ist bereits in der Datenbank eingetragen und wurde nicht erneut hinzugefügt!")
        return False

def scanFiles(path):
    print("Scanne Dateisystem nach Musikdateien! Dies kann einen Moment dauern...")
    songs = []
    count = 0
    for root, subdirs, files in os.walk(path):
        for item in files:            
            if item[-4:] == ".mp3":
                songs.append(root + "\\" + item)
                count += 1        
    print("Der Scan nach Musikdateien wurde erfolgreich abgeschlossen und es wurden " + str(count) + " Dateien gefunden!\n")
    return songs

def addFilesToDatabase(database, songs):
    for item in songs:
        addFileToDatabase(database, item)
    
def addFileToDatabase(database, path):
    song = MP3(path, EasyID3)
    if 'title' in song:
        title = str(song['title'][0])
    else:
        return

    if 'artist' in song:
        artist = str(song['artist'][0])
    else:
        artist = ""

    duration = int(song.info.length)
    addTitleToDatabase(database, path, title, artist, duration)
    
# -------------------------- Programm --------------------------

# Musikspeicherort
musicpath = 'D:\\Musik\\iTunes\\iTunes Media\\Music'

# Lokale Datenbank anlegen
dbpath = 'C:\\Users\\MEIN NAME\\AppData\\Roaming\\Music Manager'
name = 'Music.db'
checkPath(dbpath)

# Verbindungsaufbau zur Datenbank
db = createDatabase(dbpath + '\\' + name, name)
db.connect()
checkTable(db)

# Scannen der lokalen Musikdateien
files = scanFiles(musicpath)
addFilesToDatabase(db, files)

# Beenden der Verbindung
db.disconnect()
print("Music Manager wurde erfolgreich beendet!")
```

Das Problem taucht jetzt auf, wenn ich einen Ordner einlesen will, welcher ' enthält. So gibt es z.B. den Künstler K'naan, welcher eben dieses Zeichen enthält. Das Problem ist jetzt, dass SQL denkt, dass der String mit dem Künstlernamen bereits nach K' zu Ende ist und den Rest naan nicht mehr zuordnen kann, ich erhalte folgenden Fehler:

```
Traceback (most recent call last):
  File "D:\Sonstiges\Python\MusicManager.py", line 159, in <module>
    addFilesToDatabase(db, files)
  File "D:\Sonstiges\Python\MusicManager.py", line 125, in addFilesToDatabase
    addFileToDatabase(database, item)
  File "D:\Sonstiges\Python\MusicManager.py", line 140, in addFileToDatabase
    addTitleToDatabase(database, path, title, artist, duration)
  File "D:\Sonstiges\Python\MusicManager.py", line 104, in addTitleToDatabase
    database.execute("INSERT INTO songs (song_index, path, title, artist, duration, counter) VALUES (NULL, '" + path + "', '" + title + "', '" + artist + "', " + str(duration) + ", 0);")
  File "D:\Sonstiges\Python\MusicManager.py", line 39, in execute
    self.cursor.execute(code)
sqlite3.OperationalError: near "naan": syntax error
```

(Die Zeilenangaben sind falsch, ich habe hier im Forum ein paar private und experimentelle Teile entfernt  ) Jetzt frage ich mich, wie man solche Fehler umgehen kann. Gibt es dazu in der SQL-Syntax direkt einen Fix? Oder muss ich vorher im Programm solche Zeichen abfangen und dann entfernen/durch etwas anderes ersetzen? Zweiteres finde ich nicht so toll, da ja dann der Künstlername im Prinzip verfälscht wird.

Schonmal vielen Dank für Vorschläge  Vielleicht finde ich selber noch eine Lösung, dann schreibe ich das natürlich hier 
Henri


----------



## fotoman (21. Juli 2016)

Genau aus dem Grund war es schon vor >10 Jahren üblich, sich nicht seine SQL-Queries komplett von Hand zur Laufzeit aus unbekannten Daten zusammen zu basteln. Wenn da jetzt ein Liedtitel "';drop table songs;" heisst, ist die Datenbank weg  (sql injection).

Entweder (das wäre der korrekte Weg) nutzt Du Parameter in Deinen Queries, siehe z.B.
How to use variables in SQL statement in Python? - Stack Overflow
(wenn das exakt so mit sqlite in python klappt, müsste Dir die Doku aber sagen).

oder Du nutzt etwas vergleichbares zu MySQLdb.escape_string (falls python sowas für sqlite anbietet):
What is the best escape character strategy for Python/MySQL combo? - Stack Overflow

oder, als schlechteste Alternative, Du bastests Dir sowas selber. Für das Hochkomma, ist es ein simpeles, weiteres Hochkomma
How to properly escape a single quote for a SQLite database? - Stack Overflow


----------



## Stryke7 (21. Juli 2016)

Je nachdem welche Version von SQL du nutzt gibt es dafür verschiedene Lösungen um auch Strings mit '  und ähnlichem nutzen zu können.   Die einfachste ist die Nutzung von doppelten Apostrophen:  'K''Naan'

Allerdings hat fotoman Recht, dass das natürlich allgemein kein sicheres Verfahren ist.


----------



## LastChaosTyp (23. Juli 2016)

So, ich hatte gestern wenig Zeit, deswegen erst heute die Antwort 

Da ich das Projekt nur zum Ausprobieren und Zeitvertreib mache, habe ich nicht an sowas wie ne SQL-Injection gedacht, da ich meine Daten ja kenne  

Ich habe mir mal die Links angeschaut (Danke dafür ) und habe die Methode vom ersten Link verwendet.


```
def titleExistsInDatabase(database, path):
    database.cursor.execute("SELECT title FROM songs WHERE path=?", (path,))
    result = database.cursor.fetchall()
    if(result != []):
        return True
    else:
        return False
    
def addTitleToDatabase(database, path, title, artist, duration):
    print(path)
    if not titleExistsInDatabase (database, path):
        database.cursor.execute("INSERT INTO songs (song_index, path, title, artist, duration, counter) VALUES (NULL, ?, ?, ?, ?, 0)", (path, title, artist, duration))
        print("Der Musiktitel '" + title + "' wurde erfolgreich zur Datenbank hinzugefügt!")
        return True
    else:
        print("Dieser Musiktitel ist bereits in der Datenbank eingetragen und wurde nicht erneut hinzugefügt!")
        return False
```

Danke nochmal für Hilfen 
Henri


----------



## Limit (22. Dezember 2016)

Es ändert zwar nichts an der Funktionalität, aber du solltest deine Methoden innerhalb der Klasse definieren, denn ansonsten sind es eigentlich nur einfache Funktionen. Dann könntest du auch die Namen verkürzen (toDatabase weglassen).

Solltest du später mal viele Daten auf einmal einfügen wollen, solltest du execute und commit trennen. Erst alles einfügen und ganz an Schluss commit, den beim commit wird eine synchrone Schreiboperationen ausgeführt, die sehr langsam ist.

Ebenfalls eher für größere Programme interessant sind ORMs. Diese "übersetzen" zwischen Datenbank und Python, so wird z.B. aus einer Python Klasse eine Datenbanktabelle und aus einem select ein Python-Funktionsaufruf. Ich empfehle PonyORM, weil es sehr einsteigerfreundlich ist.

Ansonsten viel Spaß beim Programmieren.

Gesendet von meinem LG-V500 mit Tapatalk


----------

