Beispiel #1
0
    def get_all_projects():
        Log("Gathering local projects..")
        local = Index.get_local_projects()

        Log("Gathering remote projects..")
        remote = Index.get_remote_projects()
        projects = list(remote)

        projects.extend(x for x in local if not x.is_remote())

        return projects
Beispiel #2
0
    def download_project(self):
        Log("Downloading project..")

        # Download new cache
        if not Drive.download(self.entry.data["id"], self.get_cache_file()):
            Log("An error occurred when trying to download the project..")
            Log.press_enter()
            # something went wrong while downloading
            return False

        # Update local cache 'db.json' with new hash
        Hash.set_project_hash(self, self.entry.data["hash"])

        return True
Beispiel #3
0
    def create(folderpath, skip_if_exists=True):
        folderpath = Path(folderpath)

        if folderpath.is_file():
            Log(f'Can not create folder "{folderpath}".. This is already a file!')
        elif folderpath.is_dir():
            if skip_if_exists:
                return True
            else:
                Log(f'Can not create folder "{folderpath}".. This folder already exists!')
        else:
            os.makedirs(folderpath.absolute())
            return True

        return False
Beispiel #4
0
    def is_dirty(self):
        # If there is locally saved changes that have yet to be uploaded to the cloud
        if Dev.get("ALL_IS_DIRTY"):
            return True

        if not self.get_song_file().exists():
            Log("Studio One project file doesn't exist!", "warning")
            return False

        if not self.get_song_file(version="original").exists():
            Log("Studio One 'original' project file doesn't exist!", "warning")
            return False

        return not filecmp.cmp(self.get_song_file(),
                               self.get_song_file(version="original"),
                               shallow=False)
Beispiel #5
0
    def create_required_folders(self, temp=False, clean=True):
        # 'temp=True'  will focus on the temp/<song> directory
        # 'temp=False' will focus on the extracted_songs/<song> directory
        # 'clean=True' will delete the folders first, then recreate them

        # Need to make sure these folders exist
        Log("Creating necessary folders")

        if temp:
            # Clean out temp dir
            if clean:
                Folder.delete(self.get_temp_dir())
            Folder.create(self.get_temp_dir())
        else:
            # Create new extracted dir, remove if exists
            if clean:
                Folder.delete(self.get_root_dir())
            Folder.create(self.get_root_dir())

        for location in ["Media", "Bounces", "Mixdown"]:
            if temp:
                Folder.create(self.get_temp_dir() / location)
            Folder.create(self.get_root_dir() / location)

        return True
Beispiel #6
0
def start_main_program():
    main = Path(".main.py")

    if main.exists():
        ans = Run.prg("python", f'"{main.absolute()}"', useSubprocess=True)

        if ans == 0:
            # There was an error
            Slack.upload_log()
            raise Exception(
                "\n\nThere was an error.. Contact your administrator.\n\n")

    else:
        Log(".main.py Does not exist!!", "warning")
        Dialog(title="Fatal Error!",
               body=[
                   f'"{main.absolute()}" doesnt exist!',
                   f'\n',
                   f'\n',
                   f'Something went terribly wrong.. Contact your',
                   f'administrator.',
                   f'\n',
                   f'\n',
               ],
               clear=False).press_enter()
        Slack.upload_log()
Beispiel #7
0
    def get(key):
        data = Dev._get_data()

        if key in data:

            if "DEVELOPMENT" in data and not data["DEVELOPMENT"]:
                return False

            if data[key] and key != "DEVELOPMENT":
                Log(f'Development Mode: {key}', "warning")
                Log.press_enter()

            return data[key]
        else:
            data[key] = False
            File.set_json(FILEPATH, data)
            return False
Beispiel #8
0
    def dialog_copy_new_name(self):
        dialog = Dialog(
            title=f'Make Duplicate of "{self.entry.name}"',
            body="Please enter a new name for your duplicate project.")

        new_name = dialog.get_result("New Name").lower()
        project_names = [
            x.name.lower()
            for x in Folder.ls_folders(self.get_root_dir().parent)
        ]

        if not new_name in project_names:
            return new_name

        Log("That project name already exists.. Please try again!")
        Log.press_enter()

        return self.dialog_copy_new_name()
Beispiel #9
0
    def __init__(self, title, body, clear=True):
        self.stack = [
            f'',
            f'',
            self.create_title(title),
            f'',
            self.create_body(body),
        ]

        Log(f'Dialog: "{title}"', 'notice', quiet=True)
        self.show(self.stack, clear)
Beispiel #10
0
    def get_result(self):
        ans = input(f'   : ').lower()


        if ans == "b" and self.back:
            Log(f'Menu Answer: "back"', 'sub', quiet=True)
            print(f'')
            return "back"
        elif ans.isnumeric() and int(ans) <= len(self.options):
            result = (int(ans) -1)
            Log(f'Menu Answer: "{self.options[result]}"', 'sub', quiet=True)
            print(f'')
            return result

        print(f'')
        print(f'"{ans}" is not a valid option!')
        print(f'')
        Log(f'Menu Answer Not Valid!: "{ans}"', 'sub', quiet=True)
        Log.press_enter()

        return self.create_stack().run().get_result()
Beispiel #11
0
    def extract_project(self):
        Log("Extracting project..")

        # Create required folders in 'extracted_songs'
        self.create_required_folders()

        # Extract cache to 'temp' folder
        Log("Extracting project")
        Tar.extract(self.get_cache_file(), self.get_temp_dir().parent)

        # Copy and convert from 'temp' to 'extracted_songs'
        if not self.move_temp_to_extracted_songs():
            # If function fails, remove broken extracted project
            # to prevent issues trying again.
            Folder.delete(self.get_root_dir())
            Log("Could not move project from 'temp' to 'extracted_songs'..",
                "warning")
            Log.press_enter()
            return False

        return True
Beispiel #12
0
    def is_up_to_date(self):
        # This function checks to see if the locally extracted project
        #  is the most up-to-date project
        Log("Checking for updates..")

        # If this project is no extracted, then we are not up-to-date
        if not self.is_local():
            Log("Project is not extracted")
            return False

        ## If this project is dirty, then we are up-to-date.
        ##  We don't want to accidentally overwrite our changes!
        #if self.is_dirty():
        #    Log("Project is dirty")
        #    return True

        # If there is no remote, then it is up-to-date
        if not self.is_remote():
            Log("Project is not remote")
            return True

        # If there IS remote but not local cache, we are NOT up-to-date
        if not self.is_cached():
            Log("Project is not cached")
            return False

        # If we ARE extracted, AND remote, AND cached..
        #  Lets check if the two hashes compare
        local_hash = Hash.get_project_hash(self)
        if not local_hash:
            # We will need to re-download if local hash doesnt exist still
            #  If we reach this, something went wrong and we have to re-download
            return False

        # If local and remote hashes match, we are up-to-date
        if local_hash == self.entry.data["hash"]:
            return True

        # default return
        return False
Beispiel #13
0
    def download(ID, save_path):
        if Dev.get("NO_DOWNLOAD"):
            return True

        # We will attempt to download 5x before we give up
        i = 0
        while i < 5:
            try:
                save_path  = Path(save_path)
                request    = Drive.service.files().get_media(fileId=ID)
                fh         = io.BytesIO()
                downloader = MediaIoBaseDownload(fh, request, chunksize=51200*1024)

                print(f'----> "{save_path.name}" Downloaded 0%', end="\r", flush=True)

                done = False
                while done is False:
                    status, done = downloader.next_chunk()
                    if status:
                        print(f'----> "{save_path.name}" Downloaded {int(status.progress() * 100)}%', end="\r", flush=True)

                # If we made it this far then we should be all set
                i = 99
            except:
                i += 1
                Log(f'Download attempt #{i}..',"notice")

        # If we got a return from the Drive AND we completed the while loop
        if downloader and i==99:
            print(f'      "{save_path.name}" Downloaded successfully!')

            fh.seek(0)
            with open(save_path.absolute(), "wb") as f:
                shutil.copyfileobj(fh, f, length=1024*1024)

            return True

        Log(f'Failed to download "{save_path.name}"!',"warning")
        return False
Beispiel #14
0
    def get_mult_choice(self, options):
        if isinstance(options, list):
            optlist = "/".join(options)

        ans = ""
        while not ans in options:
            ans = input(f'  ({optlist}): ').lower()

            if not ans in options:
                print(f'\n  "{ans}" is not a valid option!\n')

        Log(f'Dialog Mult Choice Answer: "{ans}"', 'sub', quiet=True)
        return ans
Beispiel #15
0
    def duplicate(self):
        if not self.dialog_copy_confirm():
            return False

        new_name = self.dialog_copy_new_name()
        new_path = self.get_root_dir().parent / new_name

        Log(f'Duplicating "{self.entry.name}" to "{new_name}"..')

        Folder.copy(self.get_root_dir(), new_path)

        Log(f'Renaming song file', 'sub')
        File.rename(new_path / (self.get_song_file().name),
                    new_path / f'{new_name}.song')

        if self.get_song_file(version="original").exists():
            Log(f'Renaming *original song file', 'sub')
            File.rename(
                new_path / (self.get_song_file(version="original").name),
                new_path / f'{new_name}_original.song')

        Menu.notice = f'Created Project "{new_name}"!'

        return True
Beispiel #16
0
 def download_and_extract(self):
     if not self.download_project():
         Log("There was a problem downloading the update..", "warning")
         Log.press_enter()
         return False
     if not self.extract_project():
         Log("There was a problem extracting the project..", "warning")
         Log.press_enter()
         return False
     return True
Beispiel #17
0
    def confirm(self):
        self.show([
            f'',
            f'',
            self.create_title("Are you sure?"),
            f'',
            f'',
        ],
                  clear=False)

        ans = self.get_mult_choice(['y', 'n'])
        Log(f'Dialog Confirm Answer: "{ans}"', quiet=True)

        if ans == 'y':
            return True
        else:
            return False
Beispiel #18
0
    def is_cached(self):
        # If the project has a *.lof file in 'compressed_songs' directory
        #  Must check if cache AND "hash" exist
        if self.get_cache_file().exists():
            if not Hash.get_project_hash(self):
                Log(f'Project "{self.entry.name}" is cached but not hashed!',
                    "warning")
                Log.press_enter()
                return False
            Log(f'Project "{self.entry.name}" is locally cached')
            return True
        else:
            if Hash.get_project_hash(self):
                Log(f'Project "{self.entry.name}" is hashed but not cached!',
                    "warning")
                Log.press_enter()

        Log(f'Project "{self.entry.name}" is not locally cached')
        return False
Beispiel #19
0
    def to_wav(self, folderpath):
        folderpath = Path(folderpath)
        Folder.create(folderpath)

        mp3 = self.filepath
        wav = Path(f'{folderpath.absolute()}/{mp3.stem}.wav')
        stem, num, ext = File.split_name(wav.name)

        if wav.is_file():
            # File already exists locally, do not do anything
            Log(f'File "{wav.name}" already exists, keeping local file.')
            return True

        Run.ffmpeg(args="-i",
                   source=mp3.absolute(),
                   destination=wav.absolute(),
                   codec="-c:a pcm_s24le")
        return True
Beispiel #20
0
    def initialize():
        i = 0
        while i < 5:
            try:
                Drive.service = None

                # If modifying these scopes, delete the file token.pickle.
                SCOPES = ["https://www.googleapis.com/auth/drive"]
                # SCOPES = ["https://www.googleapis.com/auth/admin.directory.group"]
                creds  = None

                if os.path.exists("token.pickle"):
                    with open("token.pickle", "rb") as token:
                        creds = pickle.load(token)
                # If there are no (valid) credentials available, let the user log in.
                if not creds or not creds.valid:
                    if creds and creds.expired and creds.refresh_token:
                        creds.refresh(Request())
                    else:
                        flow = InstalledAppFlow.from_client_secrets_file(
                            "credentials.json", SCOPES)
                        creds = flow.run_local_server(port=0)
                    # Save the credentials for the next run
                    with open("token.pickle", "wb") as token:
                        pickle.dump(creds, token)

                Drive.service = build("drive", "v3", credentials=creds)

                # Root of the LOFSM project on the cloud
                Drive.root_id = Drive.get_root_id()

                i = 99
            except:
                i+=1
                Log(f'Attempting to connect to the drive.. #{i}',"notice")

        if i != 99:
            raise Exception("\n\n####  There was a problem connecting to Google Drive! Check your internet connection!")
Beispiel #21
0
def menu_open_project(filter="active"):
    # The filter argument will sort between categoried projects
    # If "all" is passed, all songs will show up
    # If "active" is passed, only songs that are categorized with "active"
    #  in the "project_type" entry field will show up.

    Log("Gathering projects, please wait..", "notice")

    projects = ProjectIndex.get_all_projects()
    if filter != "all":
        projects = [
            x for x in projects if x.entry.data["project_type"] == filter
        ]

    menu = Menu(title="What project would you like to select?",
                options=[x.create_menu_item() for x in projects])

    result = menu.get_result()

    if result == "back":
        return True
    else:
        return menu_project_options(projects[result])
Beispiel #22
0
    def extract(filepath, destination):
        filepath = Path(filepath)
        destination = Path(destination)

        Log(f'Extracting "{filepath.stem}"..')

        if not Dev.get("NO_EXTRACT"):
            try:
                os.system(
                    f"tar -xzf {filepath.absolute()} -C {destination.absolute()}"
                )
            except Exception as e:
                Log(f'Failed to extract "{filepath.stem}"')
                Log(f'\n\n{e}\n\n', None)
                Log.press_enter()
                exit()

        Log(f'Finished extracting "{filepath.stem}!"')
Beispiel #23
0
    def open_studio_one(self):
        Log("OPENING STUDIO ONE","notice")

        # First create a temp version of the project
        File.recursive_overwrite( self.get_song_file(), self.get_song_file(version="temp") )

        Dialog(
            title = "Wait for Studio One to close!",
            body  = "DO NOT CLOSE THIS WINDOW!!"
        )

        if Dev.get("NO_OPEN_STUDIO_ONE"):
            # Do not open studio one
            return True

        # Build the dummy files
        self.set_dummy_files()

        # Open Studio One
        ans = Run.prg(
            alias   = "open",
            command = f'{ self.get_song_file(version="temp") }',
            wait    = True
        )

        # Remove dummy files
        self.remove_dummy_files()

        if ans != 0:
            return False

        # Copy over any saved data to the original song file
        File.recursive_overwrite( self.get_song_file(version="temp"), self.get_song_file() )
        File.delete( self.get_song_file(version="temp") )

        return True
Beispiel #24
0
    def compress(folderpath, destination):
        folderpath = Path(folderpath)
        destination = Path(destination)
        cwd = Path.cwd()

        Log(f'Compressing "{folderpath.stem}"..')

        if not Dev.get("NO_COMPRESS"):
            try:
                os.system(
                    f'cd "{folderpath.parent}" && tar -czf "{destination.absolute()}" "{folderpath.name}" && cd "{cwd.absolute()}"'
                )
            except Exception as e:
                Log(f'Failed to compress "{folderpath.stem}"')
                Log(f'\n\n{e}\n\n', None)
                Log.press_enter()
                exit()

        Log(f'Finished compressing "{folderpath.stem}!"')
Beispiel #25
0
    def is_remote(self):
        # If the project exists on the cloud
        #  Must check if remote "id" AND "hash" both exist

        if self.entry.data["id"]:
            if not self.entry.data["hash"]:
                Log(
                    f'Project "{self.entry.name}" has remote ID but not remote hash!',
                    "warning")
                Log.press_enter()
                return False
            return True
        else:
            if self.entry.data["hash"]:
                Log(
                    f'Project "{self.entry.name}" does not have remote ID but has remote cache!',
                    "warning")
                Log.press_enter()

        return False
Beispiel #26
0
    def run_migrations():
        migrations = glob("migrations/*")
        migrations.sort()
        migrations = [Path(x) for x in migrations]
        current_version = Settings.get_version()
        result = True

        for migration in migrations:
            migration_version = Decimal(migration.stem.replace("_", "."))

            if migration_version > current_version:
                Update.install_pip_packages()

                ans = Run.prg("python",
                              migration.absolute(),
                              useSubprocess=True)

                if ans == 0:
                    Slack.upload_log()
                    Log(
                        f'There was a problem loading this migration file!\n "{migration.absolute()}"',
                        "warning")
                    Log.press_enter()
                    # If there was an issue upgrading the migration, we don't want to set the new version
                    return False
                elif ans != 1:
                    Log("You must restart the program to finish the update!",
                        "notice")
                    Log.press_enter()
                    result = False

                Settings.set_version(migration_version)

                # Push a notification to Slack
                Slack(
                    f'{Settings.get_username(capitalize=True)} has upgraded to V{migration_version}!'
                )

        return result
Beispiel #27
0
    def upload_project(self):
        # Make sure our project is the most up-to-date
        if not self.is_up_to_date():
            Log(
                "There are updates for this project on the cloud!.. can not upload!",
                "warning")
            Log.press_enter()
            return False

        # Make sure we set the category if it's never been uploaded before
        if not self.is_remote():
            self.change_category(back=False)

        if not Dev.get("NO_OPEN_STUDIO_ONE"):
            if self.dialog_remove_unused_audio_files():
                if not self.open_studio_one():
                    return False

        # Create folder for mixdowns on cloud if it doesnt exist
        if not Drive.get_id(self.entry.name):
            Drive.mkdir(self.entry.name)

        # We will need to set the local hash on our own incase of failure
        #  to upload
        if not self.compress_project(set_hash=False):
            return False

        # Upload compressed project
        Log("Uploading.. please be patient", "notice")

        if not Dev.get("NO_LOF_UPLOAD"):
            result = Drive.upload(filepath=self.get_cache_file(),
                                  mimeType=Drive.mimeType['zip'])

            if not result:
                Log("Drive upload could not complete..", "warning")
                Log.press_enter()
                return False

            # Update local hash
            Hash.set_project_hash(self, Hash.create_hash_from_project(self))

            # Update remote hash if file was successfully uploaded
            self.entry.data["id"] = result
            self.entry.data["hash"] = Hash.get_project_hash(self)
            self.entry.update()

            Slack(
                f'{Slack.get_nice_username()} uploaded a new version of {Slack.make_nice_project_name(self.entry.name)}'
            )

            # Remove name from dirty list
            self.remove_dirty()

            # Since we successfully uploaded to the drive, we can now get
            #  rid of the *original song file
            File.recursive_overwrite(self.get_song_file(),
                                     self.get_song_file(version="original"))

        Log("Compression and upload complete!", "notice")

        return True
Beispiel #28
0
    def move_extracted_song_to_temp(self):
        # Make sure we don't have any dummy files
        self.remove_dummy_files()

        for location in ["Media", "Bounces"]:
            # Remove unused cached audio from location
            # Garbage collector for unused audio files
            mp3s = Folder.ls_files(self.get_temp_dir() / location, "mp3")
            wav_names = [
                x.stem
                for x in Folder.ls_files(self.get_root_dir() / location, "wav")
            ]
            for mp3 in mp3s:
                if mp3.stem not in wav_names:
                    File.delete(mp3)

            # Convert and move wavs to mp3s
            Log(f'Converting "{location}" wav\'s to mp3\'s')
            if not Audio.folder_to_mp3(self.get_root_dir() / location,
                                       self.get_temp_dir() / location):
                return False

            # Copy over dummy.json
            Log(f'Copying over "{location}" dummy.json')
            if self.get_dummy_db(location, temp=False).exists():
                File.recursive_overwrite(
                    self.get_dummy_db(location, temp=False),
                    self.get_dummy_db(location, temp=True),
                )

        # Upload scratch files and mixdowns to the cloud
        mp3s = Folder.ls_files(self.get_temp_dir() / "Media", "mp3",
                               "Scratch*")
        mp3s.extend(
            Folder.ls_files(self.get_temp_dir() / "Media", "mp3", "SCRATCH*"))
        mp3s.extend(Folder.ls_files(self.get_root_dir() / "Mixdown", "mp3"))

        for mp3 in mp3s:
            mix_folder_id = Drive.get_id(self.entry.name)

            if not Drive.get_id(mp3.name, mix_folder_id):
                Log(f'Uploading "{mp3.name}" to the cloud', "sub")
                mp3_id = Drive.upload(filepath=mp3,
                                      mimeType=Drive.mimeType["mp3"],
                                      parent=mix_folder_id)

                audio_type = "Scratch" if mp3.parent.name == "Media" else "#MIXDOWN#"
                Slack.send_link(
                    link_name=
                    f'{audio_type} for {self.entry.name}, "{mp3.name}"',
                    ID=mp3_id)
            else:
                Log(f'Audio file "{mp3.name}" already exists on the cloud!',
                    "sub")

        # Copy over mixdowns to temp
        Log("Copying over 'Mixdown' mp3's")
        Folder.copy(self.get_root_dir() / "Mixdown",
                    self.get_temp_dir() / "Mixdown")

        # Lastly, copy over the song file
        File.recursive_overwrite(self.get_song_file(temp=False),
                                 self.get_song_file(temp=True))

        return True
Beispiel #29
0
    def to_mp3(self, folderpath, username_ignore=False):
        folderpath = Path(folderpath)

        wav = self.filepath
        mp3 = Path(f'{folderpath.absolute()}/{wav.stem}.mp3')
        username = Settings.get_username()

        if "_old" in wav.name:
            Dialog(
                title="Warning! Can't Upload *_old* Audio File!",
                body=[
                    f'It appears that you have not yet resolved the audio file',
                    f'conflict for "{wav.name}"!  You must either delete this file,',
                    f'or re-name it and re-link it in Studio One!',
                    f'\n',
                    f'\n',
                ],
                clear=False).press_enter()
            return False

        if not mp3.is_file():
            if not username in wav.name.lower() and not username_ignore:
                dialog = Dialog(
                    title="Warning! Where Is Your Name?!",
                    body=[
                        f'It appears that the file',
                        f'\n',
                        f'\n',
                        f' - {wav.parent.absolute().name}/{wav.name}',
                        f'\n',
                        f'\n',
                        f'does not contain your name.. Are you sure you recorded',
                        f'on the correct track?!  Doing this can cause serious',
                        f'version control issues!!',
                        f'\n',
                        f'\n',
                        f'Since you have already removed unused audio files from',
                        f'the pool, AND selected the checkbox to delete those',
                        f'audio files..  You should go back into your Studio One',
                        f'project, remove this clip from the timeline, OR rename',
                        f'this clip to include your name, and then re-run the',
                        f'upload!',
                        f'\n',
                        f'\n',
                        f'If you are ABSOLUTELY SURE that this is in error, aka',
                        f'uploading a project from band practice, then type "yes"',
                        f'at the prompt, or "yesall" to ignore all other warnings',
                        f'for this.',
                        f'\n',
                        f'\n',
                        f'If you want to exit now, type "no" at the prompt.',
                        f'\n',
                        f'\n',
                    ],
                    clear=False)
                ans = dialog.get_mult_choice(["yes", "yesall", "no"])

                if ans == "no":
                    return False
                elif ans == "yesall":
                    username_ignore = True

        else:
            Log(f'Keeping cached file "{mp3.name}"')
            return True

        Run.ffmpeg(args="-i",
                   source=wav.absolute(),
                   destination=mp3.absolute(),
                   codec="")

        if username_ignore:
            return {"username_ignore": True}
        return True
Beispiel #30
0
import traceback,sys
from src.Slack import Slack
from src.Update import Update
from src.Settings import Settings
from src.TERMGUI.Log import Log
from src.FileManagement.Folder import Folder
from src.menus.menu_main import menu_main

# MAIN FUNCTION
if __name__ == '__main__':

    # Create file structure if it doesn't exist
    Folder.create("compressed_songs")
    Folder.create("extracted_songs")
    Folder.create("temp")
    Folder.create("templates")

    # Create settings file if it doesn't exist
    Settings.create()

    # Start main menu
    try:
        menu_main()
    except Exception as e:
        Log(traceback.format_exc(),"warning")
        # 0 = there was a problem
        # 1 = exit gracefully
        sys.exit(0)