Lots of work on replace_files.py
This commit is contained in:
parent
2b48e889a5
commit
b5c792b8d8
@ -3,19 +3,10 @@
|
|||||||
# Script to replace existing files in parent directory. Typical usage:
|
# Script to replace existing files in parent directory. Typical usage:
|
||||||
# the current directory contains a "better" version of the file than the
|
# the current directory contains a "better" version of the file than the
|
||||||
# parent (eg, bettet bitrate).
|
# parent (eg, bettet bitrate).
|
||||||
#
|
|
||||||
# Actions:
|
|
||||||
#
|
|
||||||
# - check that the same filename is present in the parent directory
|
|
||||||
# - check that the artist and title tags are the same
|
|
||||||
# - append ".bak" to the version in the parent directory
|
|
||||||
# - move file to parent directory
|
|
||||||
# - normalise file
|
|
||||||
# - update duration, start_gap, fade_at, silence_at, mtime in database
|
|
||||||
|
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
import pydymenu
|
import pydymenu # type: ignore
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@ -34,181 +25,230 @@ from sqlalchemy.exc import IntegrityError
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
# ###################### SETTINGS #########################
|
# ###################### SETTINGS #########################
|
||||||
process_multiple_matches = True
|
process_name_and_tags_matches = True
|
||||||
do_processing = False
|
process_tag_matches = True
|
||||||
process_no_matches = False
|
do_processing = True
|
||||||
|
process_no_matches = True
|
||||||
|
|
||||||
|
source_dir = '/home/kae/music/Singles/tmp'
|
||||||
|
parent_dir = os.path.dirname(source_dir)
|
||||||
# #########################################################
|
# #########################################################
|
||||||
|
|
||||||
|
|
||||||
def insensitive_glob(pattern):
|
def insensitive_glob(pattern):
|
||||||
|
"""Helper for case insensitive glob.glob()"""
|
||||||
|
|
||||||
def either(c):
|
def either(c):
|
||||||
return '[%s%s]' % (c.lower(), c.upper()) if c.isalpha() else c
|
return '[%s%s]' % (c.lower(), c.upper()) if c.isalpha() else c
|
||||||
return glob.glob(''.join(map(either, pattern)))
|
return glob.glob(''.join(map(either, pattern)))
|
||||||
|
|
||||||
|
|
||||||
# Check file of same name exists in parent directory
|
|
||||||
source_dir = '/home/kae/music/Singles/tmp' # os.getcwd()
|
|
||||||
parent_dir = os.path.dirname(source_dir)
|
|
||||||
assert source_dir != parent_dir
|
|
||||||
|
|
||||||
name_and_tags: List[str] = []
|
name_and_tags: List[str] = []
|
||||||
name_not_tags: List[str] = []
|
|
||||||
tags_not_name: List[str] = []
|
tags_not_name: List[str] = []
|
||||||
multiple_similar: List[str] = []
|
multiple_similar: List[str] = []
|
||||||
no_match: List[str] = []
|
no_match: List[str] = []
|
||||||
possibles: List[str] = []
|
possibles: List[str] = []
|
||||||
|
no_match: int = 0
|
||||||
print(f"{source_dir=}, {parent_dir=}")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
global no_match
|
||||||
|
|
||||||
|
# We only want to run this against the production database because
|
||||||
|
# we will affect files in the common pool of tracks used by all
|
||||||
|
# databases
|
||||||
if 'musicmuster_prod' not in os.environ.get('MM_DB'):
|
if 'musicmuster_prod' not in os.environ.get('MM_DB'):
|
||||||
response = input("Not on production database - c to continue: ")
|
response = input("Not on production database - c to continue: ")
|
||||||
if response != "c":
|
if response != "c":
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
tracks = os.listdir(parent_dir)
|
|
||||||
for fname in os.listdir(source_dir):
|
|
||||||
parent_file = os.path.join(parent_dir, fname)
|
|
||||||
new_file = os.path.join(source_dir, fname)
|
|
||||||
us = get_tags(new_file)
|
|
||||||
us_t = us['title']
|
|
||||||
us_a = us['artist']
|
|
||||||
bitrate = us['bitrate']
|
|
||||||
|
|
||||||
if os.path.exists(parent_file):
|
# Sanity check
|
||||||
# File exists, check tags
|
assert source_dir != parent_dir
|
||||||
p = get_tags(parent_file)
|
|
||||||
p_t = p['title']
|
# Scan parent directory
|
||||||
p_a = p['artist']
|
with Session() as session:
|
||||||
if (
|
all_tracks = Tracks.get_all(session)
|
||||||
(str(p_t).lower() != str(us_t).lower()) or
|
parent_tracks = [a for a in all_tracks if parent_dir in a.path]
|
||||||
(str(p_a).lower() != str(us_a).lower())
|
parent_fnames = [os.path.basename(a.path) for a in parent_tracks]
|
||||||
):
|
# Create a dictionary of parent paths with their titles and
|
||||||
name_not_tags.append(
|
# artists
|
||||||
f" {fname=}, {p_t} → {us_t}, {p_a} → {us_a}")
|
parents = {}
|
||||||
process_track(new_file, parent_file, us_t, us_a, bitrate)
|
for t in parent_tracks:
|
||||||
|
parents[t.path] = {"title": t.title, "artist": t.artist}
|
||||||
|
titles_to_path = {}
|
||||||
|
artists_to_path = {}
|
||||||
|
for k, v in parents.items():
|
||||||
|
try:
|
||||||
|
titles_to_path[v['title'].lower()] = k
|
||||||
|
artists_to_path[v['artist'].lower()] = k
|
||||||
|
except AttributeError:
|
||||||
continue
|
continue
|
||||||
name_and_tags.append(new_file)
|
|
||||||
process_track(new_file, parent_file, us_t, us_a, bitrate)
|
for new_fname in os.listdir(source_dir):
|
||||||
continue
|
new_path = os.path.join(source_dir, new_fname)
|
||||||
|
new_tags = get_tags(new_path)
|
||||||
|
new_title = new_tags['title']
|
||||||
|
new_artist = new_tags['artist']
|
||||||
|
bitrate = new_tags['bitrate']
|
||||||
|
|
||||||
|
# If same filename exists in parent direcory, check tags
|
||||||
|
parent_path = os.path.join(parent_dir, new_fname)
|
||||||
|
if os.path.exists(parent_path):
|
||||||
|
parent_tags = get_tags(parent_path)
|
||||||
|
parent_title = parent_tags['title']
|
||||||
|
parent_artist = parent_tags['artist']
|
||||||
|
if (
|
||||||
|
(str(parent_title).lower() == str(new_title).lower()) and
|
||||||
|
(str(parent_artist).lower() == str(new_artist).lower())
|
||||||
|
):
|
||||||
|
name_and_tags.append(
|
||||||
|
f" {new_fname=}, {parent_title} → {new_title}, "
|
||||||
|
f" {parent_artist} → {new_artist}"
|
||||||
|
)
|
||||||
|
if process_name_and_tags_matches:
|
||||||
|
process_track(new_path, parent_path, new_title,
|
||||||
|
new_artist, bitrate)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check for matching tags although filename is different
|
||||||
|
if new_title.lower() in titles_to_path:
|
||||||
|
possible_path = titles_to_path[new_title.lower()]
|
||||||
|
if parents[possible_path]['artist'].lower() == new_artist.lower():
|
||||||
|
# print(
|
||||||
|
# f"title={new_title}, artist={new_artist}:\n"
|
||||||
|
# f" {new_path} → {parent_path}"
|
||||||
|
# )
|
||||||
|
tags_not_name.append(
|
||||||
|
f"title={new_title}, artist={new_artist}:\n"
|
||||||
|
f" {new_path} → {parent_path}"
|
||||||
|
)
|
||||||
|
if process_tag_matches:
|
||||||
|
process_track(new_path, possible_path, new_title,
|
||||||
|
new_artist, bitrate)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
no_match += 1
|
||||||
|
|
||||||
|
else:
|
||||||
|
no_match += 1
|
||||||
|
|
||||||
# Try to find a near match
|
# Try to find a near match
|
||||||
stem = fname.split(".")[0]
|
|
||||||
matches = insensitive_glob(os.path.join(parent_dir, stem) + '*')
|
|
||||||
match_count = len(matches)
|
|
||||||
if match_count == 0:
|
|
||||||
if process_no_matches:
|
|
||||||
prompt = f"\n file={fname}\n title={us_t}\n artist={us_a}: "
|
|
||||||
# Use fzf to search
|
|
||||||
choice = pydymenu.fzf(tracks, prompt)
|
|
||||||
if choice:
|
|
||||||
old_file = os.path.join(parent_dir, choice[0])
|
|
||||||
oldtags = get_tags(old_file)
|
|
||||||
old_title = oldtags['title']
|
|
||||||
old_artist = oldtags['artist']
|
|
||||||
print()
|
|
||||||
print(f" File name will change {choice[0]}")
|
|
||||||
print(f" → {fname}")
|
|
||||||
print()
|
|
||||||
print(f" Title tag will change {old_title}")
|
|
||||||
print(f" → {us_t}")
|
|
||||||
print()
|
|
||||||
print(f" Artist tag will change {old_artist}")
|
|
||||||
print(f" → {us_a}")
|
|
||||||
print()
|
|
||||||
data = input("Go ahead (y to accept)? ")
|
|
||||||
if data == "y":
|
|
||||||
process_track(new_file, old_file, us_t, us_a, bitrate)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
no_match.append(f"{fname}, {us_t=}, {us_a=}")
|
|
||||||
continue
|
|
||||||
no_match.append(f"{fname}, {us_t=}, {us_a=}")
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
no_match.append(f"{fname}, {us_t=}, {us_a=}")
|
|
||||||
continue
|
|
||||||
|
|
||||||
if match_count > 1:
|
# stem = new_fname.split(".")[0]
|
||||||
multiple_similar.append(fname + "\n " + "\n ".join(matches))
|
# matches = insensitive_glob(os.path.join(parent_dir, stem) + '*')
|
||||||
if match_count <= 26 and process_multiple_matches:
|
# match_count = len(matches)
|
||||||
print(f"\n file={fname}\n title={us_t}\n artist={us_a}\n")
|
# if match_count == 0:
|
||||||
d = {}
|
if process_no_matches:
|
||||||
while True:
|
prompt = f"\n file={new_fname}\n title={new_title}\n artist={new_artist}: "
|
||||||
for i, match in enumerate(matches):
|
# Use fzf to search
|
||||||
d[i] = match
|
choice = pydymenu.fzf(parent_fnames, prompt)
|
||||||
for k, v in d.items():
|
if choice:
|
||||||
print(f"{k}: {v}")
|
old_file = os.path.join(parent_dir, choice[0])
|
||||||
data = input("pick one, return to quit: ")
|
oldtags = get_tags(old_file)
|
||||||
if data == "":
|
old_title = oldtags['title']
|
||||||
break
|
old_artist = oldtags['artist']
|
||||||
try:
|
print()
|
||||||
key = int(data)
|
print(f" File name will change {choice[0]}")
|
||||||
except ValueError:
|
print(f" → {new_fname}")
|
||||||
continue
|
print()
|
||||||
if key in d:
|
print(f" Title tag will change {old_title}")
|
||||||
dst = d[key]
|
print(f" → {new_title}")
|
||||||
process_track(new_file, dst, us_t, us_a, bitrate)
|
print()
|
||||||
break
|
print(f" Artist tag will change {old_artist}")
|
||||||
else:
|
print(f" → {new_artist}")
|
||||||
continue
|
print()
|
||||||
continue # from break after testing for "" in data
|
data = input("Go ahead (y to accept)? ")
|
||||||
# One match, check tags
|
if data == "y":
|
||||||
sim_name = matches[0]
|
process_track(new_path, old_file, new_title, new_artist, bitrate)
|
||||||
p = get_tags(sim_name)
|
continue
|
||||||
p_t = p['title']
|
if data == "q":
|
||||||
p_a = p['artist']
|
sys.exit(0)
|
||||||
if (
|
else:
|
||||||
(str(p_t).lower() != str(us_t).lower()) or
|
continue
|
||||||
(str(p_a).lower() != str(us_a).lower())
|
# else:
|
||||||
):
|
# no_match.append(f"{new_fname}, {new_title=}, {new_artist=}")
|
||||||
possibles.append(
|
# continue
|
||||||
f"File: {os.path.basename(sim_name)} → {fname}"
|
|
||||||
f"\n {p_t} → {us_t}\n {p_a} → {us_a}"
|
# if match_count > 1:
|
||||||
)
|
# multiple_similar.append(new_fname + "\n " + "\n ".join(matches))
|
||||||
process_track(new_file, sim_name, us_t, us_a, bitrate)
|
# if match_count <= 26 and process_multiple_matches:
|
||||||
continue
|
# print(f"\n file={new_fname}\n title={new_title}\n artist={new_artist}\n")
|
||||||
tags_not_name.append(f"Rename {os.path.basename(sim_name)} → {fname}")
|
# d = {}
|
||||||
process_track(new_file, sim_name, us_t, us_a, bitrate)
|
# while True:
|
||||||
|
# for i, match in enumerate(matches):
|
||||||
|
# d[i] = match
|
||||||
|
# for k, v in d.items():
|
||||||
|
# print(f"{k}: {v}")
|
||||||
|
# data = input("pick one, return to quit: ")
|
||||||
|
# if data == "":
|
||||||
|
# break
|
||||||
|
# try:
|
||||||
|
# key = int(data)
|
||||||
|
# except ValueError:
|
||||||
|
# continue
|
||||||
|
# if key in d:
|
||||||
|
# dst = d[key]
|
||||||
|
# process_track(new_path, dst, new_title, new_artist, bitrate)
|
||||||
|
# break
|
||||||
|
# else:
|
||||||
|
# continue
|
||||||
|
# continue # from break after testing for "" in data
|
||||||
|
# # One match, check tags
|
||||||
|
# sim_name = matches[0]
|
||||||
|
# p = get_tags(sim_name)
|
||||||
|
# parent_title = p['title']
|
||||||
|
# parent_artist = p['artist']
|
||||||
|
# if (
|
||||||
|
# (str(parent_title).lower() != str(new_title).lower()) or
|
||||||
|
# (str(parent_artist).lower() != str(new_artist).lower())
|
||||||
|
# ):
|
||||||
|
# possibles.append(
|
||||||
|
# f"File: {os.path.basename(sim_name)} → {new_fname}"
|
||||||
|
# f"\n {parent_title} → {new_title}\n {parent_artist} → {new_artist}"
|
||||||
|
# )
|
||||||
|
# process_track(new_path, sim_name, new_title, new_artist, bitrate)
|
||||||
|
# continue
|
||||||
|
# tags_not_name.append(f"Rename {os.path.basename(sim_name)} → {new_fname}")
|
||||||
|
# process_track(new_path, sim_name, new_title, new_artist, bitrate)
|
||||||
|
|
||||||
print(f"Name and tags match ({len(name_and_tags)}):")
|
print(f"Name and tags match ({len(name_and_tags)}):")
|
||||||
# print(" \n".join(name_and_tags))
|
# print(" \n".join(name_and_tags))
|
||||||
# print()
|
# print()
|
||||||
|
|
||||||
print(f"Name but not tags match ({len(name_not_tags)}):")
|
# print(f"Name but not tags match ({len(name_not_tags)}):")
|
||||||
print(" \n".join(name_not_tags))
|
# print(" \n".join(name_not_tags))
|
||||||
print()
|
# print()
|
||||||
|
|
||||||
print(f"Tags but not name match ({len(tags_not_name)}):")
|
print(f"Tags but not name match ({len(tags_not_name)}):")
|
||||||
# print(" \n".join(tags_not_name))
|
# print(" \n".join(tags_not_name))
|
||||||
# print()
|
# print()
|
||||||
|
|
||||||
print(f"Multiple similar names ({len(multiple_similar)}):")
|
# print(f"Multiple similar names ({len(multiple_similar)}):")
|
||||||
print(" \n".join(multiple_similar))
|
# print(" \n".join(multiple_similar))
|
||||||
print()
|
# print()
|
||||||
|
|
||||||
print(f"Possibles: ({len(possibles)}):")
|
# print(f"Possibles: ({len(possibles)}):")
|
||||||
print(" \n".join(possibles))
|
# print(" \n".join(possibles))
|
||||||
print()
|
# print()
|
||||||
|
|
||||||
print(f"No match ({len(no_match)}):")
|
# print(f"No match ({len(no_match)}):")
|
||||||
print(" \n".join(no_match))
|
# print(" \n".join(no_match))
|
||||||
print()
|
# print()
|
||||||
|
|
||||||
print(f"Name and tags match ({len(name_and_tags)}):")
|
# print(f"Name and tags match ({len(name_and_tags)}):")
|
||||||
print(f"Name but not tags match ({len(name_not_tags)}):")
|
# print(f"Name but not tags match ({len(name_not_tags)}):")
|
||||||
print(f"Tags but not name match ({len(tags_not_name)}):")
|
# print(f"Tags but not name match ({len(tags_not_name)}):")
|
||||||
print(f"Multiple similar names ({len(multiple_similar)}):")
|
# print(f"Multiple similar names ({len(multiple_similar)}):")
|
||||||
print(f"Possibles: ({len(possibles)}):")
|
# print(f"Possibles: ({len(possibles)}):")
|
||||||
print(f"No match ({len(no_match)}):")
|
# print(f"No match ({len(no_match)}):")
|
||||||
|
print(f"No matches: {no_match}")
|
||||||
|
|
||||||
|
|
||||||
def process_track(src, dst, title, artist, bitrate):
|
def process_track(src, dst, title, artist, bitrate):
|
||||||
|
|
||||||
new_path = os.path.join(os.path.dirname(dst), os.path.basename(src))
|
new_path = os.path.join(os.path.dirname(dst), os.path.basename(src))
|
||||||
print(
|
print(
|
||||||
f"process_track:\n {src=}\n {new_path=}\n "
|
f"process_track:\n {src=}\n {dst=}\n {title=}, {artist=}\n"
|
||||||
f"{dst=}\n {title=}, {artist=}\n"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not do_processing:
|
if not do_processing:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user