#!/usr/bin/python3 # -*- coding: utf-8 -*- """ IMM - Internet Media Manager: Play internet media in desktop media player just like local files and optionally save them offline. IMM can function as conventional media downloaders as well. Copyright (C) 2018-2020 IMM@radii.dev This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . """ """ NOTE: IMPROVE CODE, FIX REPETIONS AND MESSES. GO THROUGH ENTIRE CODE. REFACTOR CODEBASE. Use tuple where list is not required scuh as constant & non-global data. Below, first several functions are defined. To read squentially, go to the bottom and start reading from execution/calling of Main() function. VD is VariablesDictionary. see GenerateVariablesDictionary() function. """ # Python standard library imports import subprocess import sys import time import os import threading import glob import shlex import pathlib # Third-party imports # import youtube-dl try: import pyperclip except: print( "WARNING: pyperclip module not found. Please install pyperclip" " module https://pypi.org/project/pyperclip/ in your python " "setup. It is required to automatically fetch urls from " "clipboard. Loading pyperclip module provided in programme but" " it might not work", file=sys.stderr) try: from Code.PythonModules import pyperclip except: # symbolic function if failed to import class pyperclip: def paste(): return '' # Current programme imports from Code.IMM.GeneralFunctions.InputOutput import ( ReadTextFile, ReadlinesTextFile, WriteGeneralTextFiles) from Code.IMM.GeneralFunctions.DataStructureManupulations import ( ConvertToStandardPathFormat, GetTextAfter, DoubleQuoteString, SingleQuoteString, ListIntoString, StandardVariableName) class FindMediaURLs: """ First look for media urls in programme arguments than clipboard and finally ask for user input if still not found. """ def __init__(self): MediaURLs = self.ExtractingMediaUrls(Arguments) if len(MediaURLs) != 0: self.MediaURLs = MediaURLs return # Causing Gtk-WARNING **: Theme parsing error: Clipboard = pyperclip.paste() Clipboard = Clipboard.split('\n') MediaURLs = self.ExtractingMediaUrls(Clipboard) if len(MediaURLs) != 0: self.MediaURLs = MediaURLs return MediaURLs = self.ProcessUserInput() self.MediaURLs = MediaURLs def ProcessUserInput(self): print( "No media url found in aguments and clipboard. You can " "paste media urls and imm created m3u files location " "below. Paste one url per line (empty input will break the" " url collection loop). This way you can paste list of " "mutiple urls with one url per line (bulk url paste will " "work)") MediaURLList = '' while len(MediaURLList) == 0: UserInputList = [] while True: UserInput = input() UserInputList.append(UserInput) if len(UserInput) == 0: break MediaURLList = self.ExtractingMediaUrls(UserInputList) if len(MediaURLList) != 0: return MediaURLList print("No valid media url or m3u file cointaing valid " "media url found. Try again!") def ExtractingMediaUrls(self, Source): MediaURLs = [] for i in range(len(Source)): Item = Source[i].strip(Quotes + ' ') if Item.casefold().startswith(VD['validmediaurlstart']): MediaURL = DoubleQuoteString(Item) MediaURLs.append(MediaURL) elif Item.casefold().endswith( VD['validextensioncontaningmediaurl1']): M3UFile = ConvertToStandardPathFormat(Item) MediaAvailableOffline = \ self.M3UFile_IsMediaAvailableOffline(M3UFile) if len(MediaAvailableOffline) > 0: MediaURLs.append(MediaAvailableOffline) else: MediaURL = self.GetMediaURLFromM3UFile(M3UFile) if MediaURL != None: MediaURL = DoubleQuoteString(MediaURL) MediaURLs.append(MediaURL) return MediaURLs def GetMediaURLFromM3UFile(self, M3UFile): if os.path.isfile(M3UFile) == True: FileLines = ReadlinesTextFile(M3UFile) MediaURL = GetTextAfter('#Source: ', FileLines).strip(' ') if len(MediaURL) > 0: return MediaURL def M3UFile_IsMediaAvailableOffline(self, M3UFile): Return = '' FileLines = ReadlinesTextFile(M3UFile) ChangeWorkingDirectoryTo = os.path.split(M3UFile)[0] os.chdir(ChangeWorkingDirectoryTo) for i in range(len(FileLines)): Line = FileLines[i].strip('\n' + Quotes) if Line.startswith('#') or\ Line.casefold().startswith(VD['validmediaurlstart']): continue else: Line = os.path.abspath(Line) if os.path.isfile(Line) == True: Return ='Offline://' + Line os.chdir(ProgrammeDirectory) return Return class CalculateMediaQualityPreference: def __init__(self, DownloadOnly = False): InternetSpeed = VD['internetspeed'] MediaQualityHeight = VD['mediaqualityheight'] PreferAudioOnly = VD['preferaudioonly'] self.AllowSeprateVideoAndAudioSource = True if DownloadOnly == False: if (len(MediaPlayerMultipleSourcePlayCommand) <= 0 or VD['allowseprateaudiosource'] != "yes"): self.AllowSeprateVideoAndAudioSource = False CustomFormatOptions = VD['customformatoptions'].strip(' "\'/') if len(CustomFormatOptions) > 0: CustomFormatOptions += '/' SpeedBasedPreference = '' QualityBasedPreference = '' TryAudioFirst = '' if InternetSpeed != None: MaxAverageBitrate = 0.8 * int(InternetSpeed) MaxAverageBitrateString = str(MaxAverageBitrate) MaxAverageBitrate2 = MaxAverageBitrate - 320 MaxAverageBitrate2String = str(MaxAverageBitrate2) SpeedBasedPreference = ( "bestvideo[tbr<=" + MaxAverageBitrate2String + "]+bestaudio/worstvideo[tbr>=" + MaxAverageBitrate2String + "]+bestaudio/best[tbr<=" + MaxAverageBitrateString + "]/worst[tbr>=" + MaxAverageBitrateString + "]/") if MediaQualityHeight != None: MediaQualityHeight = str(MediaQualityHeight) QualityBasedPreference = ( "bestvideo[height<=" + MediaQualityHeight + "]+bestaudio/worstvideo[height>=" + MediaQualityHeight + "]+bestaudio/best[height<=" + MediaQualityHeight + "]/worst[height>=" + MediaQualityHeight + "]/") if (PreferAudioOnly == 'yes' or Arguments.count('--preferaudioonly') != 0): if InternetSpeed != None: TryAudioFirst = ( "bestaudio[tbr<=" + MaxAverageBitrateString +"']/worst[tbr>=" + MaxAverageBitrateString + "]/") else: TryAudioFirst = "bestaudio/" MediaPreference = ( TryAudioFirst + CustomFormatOptions + SpeedBasedPreference + QualityBasedPreference + "best") if self.AllowSeprateVideoAndAudioSource == False: MediaPreference = self.RemoveMutipleSourceFormats( MediaPreference) self.MediaPreference = MediaPreference def RemoveMutipleSourceFormats(self, MediaPreference): MediaPreferencelist = MediaPreference.split('/') MediaPreference = [] for i in range(len(MediaPreferencelist)): if MediaPreferencelist[i].find('+') == -1: MediaPreference.append(MediaPreferencelist[i]) MediaPreference = '/'.join(MediaPreference) return MediaPreference def ManuallySelectFormat(self, MediaURL): ManuallySelectFormat = VD['manuallyselectformat'] if (ManuallySelectFormat == 'yes' or Arguments.count('--manuallyselectformat') != 0): print('Finding available formats...') GetAvailableFormatsCommand = ' '.join(( YoutubeDL, "--list-formats --no-playlist", MediaURL)) OutputString = RunSubprocessAndReturnOutputString( GetAvailableFormatsCommand) FoundFormats, FormatStartAt = self.DidFoundAnyFormat( OutputString) if FoundFormats == False: print( "Could not find any format. Press return key to " " process next url in queue if any, else to exit. " "Possible reasons: \n" "1. Probably bug in IMM, report issue to us.\n" "2. Unsupported website or supported but still not" " working, report it to youtube-dl developers\n" "3. Incorrect url or url has no media. check " "again.\n" "below is the output from youtube-dl:\n" + OutputString) input() return 'Failed' InvalidInputTryAgainOrSkip = ( "Invalid format code. Try again or type 's' or 'skip'" " to process media with automatic format selection.") print( "Type code of one (example 'videoandaudio') or two " "(example 'videos+audio') of the available formats " "below to process it. You can add a blank space ' ' " "before to keep manuallyselectformat turned on, by " "default its automaically turned off. Add blank space " "' ' after code to add it to customformatoptions so " "that you do not have to manully select it again and " "again.\n" + OutputString[FormatStartAt:]) VerificationCode = -1 while VerificationCode == -1: UserInput = input() if len(UserInput) == 0 : print(InvalidInputTryAgainOrSkip) continue if UserInput.startswith(' '): KeepManuallySelectFormatON = True else: KeepManuallySelectFormatON = False if UserInput.endswith(' '): AddToCustomFormatOptions = True else: AddToCustomFormatOptions = False UserInput = UserInput.strip(' ') MultipleFormatSelected = UserInput.find('+') if MultipleFormatSelected == -1: Verification = '\n' + UserInput VerificationCode = OutputString.find(Verification) else: if self.AllowSeprateVideoAndAudioSource == True: FirstVerification = ( '\n' + UserInput[:MultipleFormatSelected]) FirstVerificationCode = OutputString.find( FirstVerification) LastVerification = ( '\n' + UserInput[(MultipleFormatSelected + 1):]) LastVerificationCode = OutputString.find( LastVerification) if FirstVerification != -1 and LastVerificationCode != -1: VerificationCode = 1 else: print( "Media player do not support seprate " "video+audio source playback. Select single" " videoandaudio or audioonly format") if VerificationCode != -1: UserSelectedFormat = UserInput + '/' if KeepManuallySelectFormatON == False: Configuration.EditConfigurationFile( 'manuallyselectformat', 'no') if AddToCustomFormatOptions == True: AlreadyInConfiguration = \ VD['CustomFormatOptions'].strip('/ ') UpdatedCustomFormatOptions = ( UserSelectedFormat + AlreadyInConfiguration).strip('/ ') Configuration.EditConfigurationFile( 'CustomFormatOptions', UpdatedCustomFormatOptions) break if UserInput == 's' or UserInput == 'skip': UserSelectedFormat = '' break print(InvalidInputTryAgainOrSkip) else: UserSelectedFormat = '' MediaPreference = DoubleQuoteString( UserSelectedFormat + self.MediaPreference) MediaPreference = "--format " + MediaPreference return MediaPreference def DidFoundAnyFormat(self, OutputString): """ When ManuallySelectFormat is enabled, it tests if found any format/media source links or not. """ FindTheseInSequence = ( 'format', 'code', 'extension', 'resolution', 'note') b = 0 for Item in range(len(FindTheseInSequence)): a = OutputString[b:].find(FindTheseInSequence[Item]) if a == -1: return False, 0 b += a FormatStartAt = OutputString[:b].rfind('\n') + 1 return True, FormatStartAt def ParseDomainName(MediaURL): """ Parse domain name from url for naming m3u files in Previous list and Watch Later list. """ DomainStart = MediaURL.find("//") + 2 MediaURLFactor2 = MediaURL[DomainStart:].find('/') DomainEnd = DomainStart + MediaURLFactor2 MediaDomain = MediaURL[DomainStart : DomainEnd] if MediaDomain.startswith("www."): MediaDomain = MediaDomain[4:] return MediaDomain def ConvertDurationIntoSeconds(MediaDuration): """ Example: Input:12:05 => Output: 725 """ DurationsList = MediaDuration.split(':') DurationsListLength = len(DurationsList) MutiplyingFactor = 1 DurationInSeconds = 0 for i in range(len(DurationsList) - 1, -1, -1): Add = int(DurationsList[i]) * MutiplyingFactor DurationInSeconds += Add MutiplyingFactor *= 60 return str(DurationInSeconds) class PlayOnly: def __init__(self, MediaURL, MediaPreference): self.MediaURL = MediaURL self.MediaPreference = MediaPreference """ This won't work it won't have custom configuration like audio only, it will have seprate configuration, duplication etc. and therefore defeating the purpose of IMM. Find how to pass Arguments in gnome-mpv and how to send additonal media urls to already playing gnome-mpv instance. No need of it any since mpv itself don't have instance control either (not sure it gnome-mpv have instance control or not yet. read documentation on website or talk on IRC). POSSIBLE FIX: read path of gnome-mpv's mpv configuration path in gnome-mpv config folder and copy it and add arguments as commands in copied file and specify that in argument of execution of gnome-mpv. If no option to specify in arguments than specify in configuration file and than execute it, than after playing, restore gnome-mpv configuration file. if no config file found than create it and than delete after playing. THIS CAN WORK. Push changes upstream to support this feature. """ IsMediaPlayedAlready = self.DirectlyPlayForSomeMediaPlayers() if IsMediaPlayedAlready != True: self.MediaDomain = ParseDomainName(self.MediaURL) PrimaryMethod = self.PrimaryPlayingMethod() if PrimaryMethod == 'failed': self.AlternatePlayingMethod() # Kill this function def DirectlyPlayForSomeMediaPlayers(self): if (MediaPlayer.endswith('gnome-mpv') or MediaPlayer.endswith('gnome-mpv.exe')): PlayMedia(MediaURLListString) return True return False def AlternatePlayingMethod(self): print('Wait... extracting media url') ExtractURL = ' '.join(( YoutubeDL, "--get-url --no-playlist", self.MediaPreference, self.MediaURL)) ExtractedURL = RunSubprocessAndReturnOutputString( ExtractURL).strip('\n') QuoteExtractedURL = DoubleQuoteString(ExtractedURL) StartProcessing.ProcessNextURL = True PlayMedia(QuoteExtractedURL) def PrimaryPlayingMethod(self): print('Wait... extracting media url') ExtractURLTitleAndDuration = ' '.join(( YoutubeDL, "--get-title --get-url --get-duration --no-playlist", AdditionalYoutubeDLArguments, self.MediaPreference, self.MediaURL)) ExtractedURLTitleAndDurationList = RunSubprocessAndReturnOutputString( ExtractURLTitleAndDuration).strip('\n').split('\n') LenOfExtractedURLTitleAndDurationList = \ len(ExtractedURLTitleAndDurationList) ExtractedAudioURL = None PrintErrorMessageIfFails = ( "Something went wrong with extraction process. Below " "(or above) is the extracted string:\n" + ('\n').join(ExtractedURLTitleAndDurationList)) if LenOfExtractedURLTitleAndDurationList == 4: if ExtractedURLTitleAndDurationList[2].find( ExtractedURLTitleAndDurationList[1][:4]) != -1: MediaTitle = ExtractedURLTitleAndDurationList[0] ExtractedURL = ExtractedURLTitleAndDurationList[1] ExtractedAudioURL = ExtractedURLTitleAndDurationList[2] ExtractedAudioURL = DoubleQuoteString(ExtractedAudioURL) MediaDuration = ExtractedURLTitleAndDurationList[3] MediaDurationInSeconds = ConvertDurationIntoSeconds(MediaDuration) else: print(PrintErrorMessageIfFails) return 'failed' elif LenOfExtractedURLTitleAndDurationList == 3: """ Test if both start with same protocol like http. Than both are media urls (one is video source and other is audio source), otherwise other one is MediaDuration. """ if ExtractedURLTitleAndDurationList[2].find( ExtractedURLTitleAndDurationList[1][:4]) != -1: MediaTitle = ExtractedURLTitleAndDurationList[0] ExtractedURL = ExtractedURLTitleAndDurationList[1] ExtractedAudioURL = ExtractedURLTitleAndDurationList[2] ExtractedAudioURL = DoubleQuoteString(ExtractedAudioURL) else: MediaTitle = ExtractedURLTitleAndDurationList[0] ExtractedURL = ExtractedURLTitleAndDurationList[1] MediaDuration = ExtractedURLTitleAndDurationList[2] MediaDurationInSeconds = ConvertDurationIntoSeconds( MediaDuration) elif LenOfExtractedURLTitleAndDurationList == 2: MediaTitle = ExtractedURLTitleAndDurationList[0] ExtractedURL = ExtractedURLTitleAndDurationList[1] MediaDurationInSeconds = '' else: print(PrintErrorMessageIfFails) return 'failed' M3UFileName = self.MediaDomain + ' → ' + MediaTitle + '.m3u' for Symbols in range(len(VD['ReplaceTheseSymbols'])): M3UFileName = M3UFileName.replace( VD['ReplaceTheseSymbols'][Symbols], VD['ReplaceWithSymbols'][Symbols]) AudioSourceLine = '' if ExtractedAudioURL != None: AudioSourceLine = '#Audio:' + ExtractedAudioURL """ Known Issue: for now, atleast 1 ASCII character would be required in name to work in Windows since can't get unicode output in Windows. """ DirectoryAndM3UFileName = os.path.join(VD['previouslist'], M3UFileName) WriteToM3UFile( DirectoryAndM3UFileName, MediaDuration = MediaDurationInSeconds, MediaTitle = MediaTitle, MediaLocation = ExtractedURL, AddtionalText = AudioSourceLine, MediaURL = self.MediaURL) DirectoryAndM3UFileName2 = os.path.join(VD['previouslist2'], M3UFileName) WriteToM3UFile( DirectoryAndM3UFileName2, MediaDuration = MediaDurationInSeconds, MediaTitle = MediaTitle, MediaLocation = ExtractedURL, AddtionalText = AudioSourceLine, MediaURL = self.MediaURL) StartProcessing.ProcessNextURL = True DirectoryAndM3UFileName2 = DoubleQuoteString( DirectoryAndM3UFileName2) # M3UFileName2 = '"' + M3UFileName + '"' if (MediaPlayer.endswith('vlc') or MediaPlayer.endswith('vlc.exe')): quotedExtractedURL = DoubleQuoteString(ExtractedURL) PlayMedia( DirectoryAndM3UFileName2, SeprateAudio = True, WorkaroundForVLC = quotedExtractedURL, VLCWorkAroundTitle = MediaTitle) else: PlayMedia(DirectoryAndM3UFileName2, SeprateAudio = True) #threading.Thread(target = PlayMedia(DirectoryAndM3UFileName2)).start() # REWRITE FUNCTION WITH BETTER CLASSES-OBJECTS UNDERSTANDING class PlayAndSave: def __init__(self, MediaNumber, MediaURL, MediaPreference): self.MediaNumber = MediaNumber self.MediaURL = MediaURL self.MediaPreference = MediaPreference self.FileNameExtracted = False self.FinalFileName = False self.AudioSource = None self.DeleteFilesTuple = () self.MediaTitle = '' self.FormatsMerged = False self.DirectoryAndFile = '' self.FileName = '' self.PlayingSeprateFile = False self.MediaDomain = ParseDomainName(MediaURL) threading.Thread( target = self.GetAudioUrlIfSeprateAudioSource).start() threading.Thread(target = self.Play).start() #threading.Thread(target = self.DeleteFiles).start() self.Save() def GetAudioUrlIfSeprateAudioSource(self): IsSeprateAudioStreamCommand = ' '.join(( YoutubeDL, "-e -g --no-playlist", AdditionalYoutubeDLArguments, self.MediaPreference, self.MediaURL)) IsSeprateAudioStream = RunSubprocessAndReturnOutputString( IsSeprateAudioStreamCommand).strip('\n').split('\n') self.MediaTitle = DoubleQuoteString( IsSeprateAudioStream[0]).strip('"') if len(IsSeprateAudioStream) > 2: print('\nSeprate Audio Source') self.AudioSource = DoubleQuoteString( IsSeprateAudioStream[2]) #1=vdeo 2=audio else: self.AudioSource = '' def Save(self): print('Saving media... Will play as soon as its playable') SaveCommand = ' '.join(( YoutubeDL, "--no-part --keep-video --no-playlist", AdditionalYoutubeDLArguments, self.MediaPreference, self.MediaURL)) SaveCommand = shlex.split(SaveCommand) SaveMedia = subprocess.Popen( SaveCommand, cwd = VD['offlinemedia'], stdout = subprocess.PIPE, universal_newlines = True) while SaveMedia.poll() is None: Output = SaveMedia.stdout.readline() # Do not end with '\n' newline. Stay on same line. print(Output.strip('\n'), end = '', flush = True) """ Backspace/clear output to print next in same line """ print('\r', end = '') self.ExtractFileName(Output) StartProcessing.ProcessNextURL = True def ExtractFileName(self, Output): if Output.startswith('[download] Destination: '): self.FileName = Output[24:-1] self.AfterFileNameExtracted() elif Output.startswith('[ffmpeg] Merging formats into "'): self.FileName = Output[31:-2] self.AfterFileNameExtracted(ThisIsFinalFileName = True) self.FinalFileName = True self.FormatsMerged = True elif Output.endswith(' has already been downloaded and merged\n'): self.FileName = Output[11:-40] self.AfterFileNameExtracted(ThisIsFinalFileName = True) self.FinalFileName = True self.FormatsMerged = True elif Output.endswith(' has already been downloaded\n'): self.FileName = Output[11:-29] self.AfterFileNameExtracted() def AfterFileNameExtracted(self, ThisIsFinalFileName = False): self.DirectoryAndFile = VD['offlinemedia'] + self.FileName if ThisIsFinalFileName == False: self.DeleteFilesTuple += self.DirectoryAndFile, self.FileNameExtracted = True def Play(self): global PlayingMediaNumber, PlayingSeprateFile while PlayingMediaNumber < self.MediaNumber: time.sleep(1/10) while self.AudioSource == None: time.sleep(1/10) while self.FileNameExtracted == False: time.sleep(1/10) while os.path.isfile(self.DirectoryAndFile) is False: time.sleep(1/10) MinimumRequiredSize = (1/2) * 1048576 # 0.5MB while os.path.getsize(self.DirectoryAndFile) < MinimumRequiredSize: time.sleep(1/10) threading.Thread(target = self.CreateM3UFile).start() DirectoryAndFile = DoubleQuoteString(self.DirectoryAndFile) if len(self.AudioSource) > 0 and self.FormatsMerged == False: PlayingSeprateFile = True PlayMedia( DirectoryAndFile, SeprateAudio = True, AudioSource = self.AudioSource) PlayingSeprateFile = False else: PlayMedia(DirectoryAndFile) PlayingMediaNumber += 1 """ This is temporary until fix PlayingSeprateFile variable issue in DeleteFiles() """ self.DeleteFiles() def DeleteFiles(self): while self.AudioSource == None: time.sleep(1) if len(self.AudioSource) == 0: return while self.FormatsMerged == False: time.sleep(1) while os.path.isfile(self.DirectoryAndFile) == False: time.sleep(1) # Do not delete if file is still being merged i.e. increasing size while True: a = os.path.getsize(self.DirectoryAndFile) time.sleep(5) b = os.path.getsize(self.DirectoryAndFile) if a == b: break while self.PlayingSeprateFile == True: #why this not working? #print(self.PlayingSeprateFile) time.sleep(1) for File in range(len(self.DeleteFilesTuple)): FileToBeDeleted = self.DeleteFilesTuple[File] print( 'Files are Merged, Deleting Seprate File ' + FileToBeDeleted) os.remove(FileToBeDeleted) def CreateM3UFile(self): if len(self.AudioSource) > 0: while self.FinalFileName == False: time.sleep(1) M3UFileName = self.MediaDomain + ' → ' + self.MediaTitle + '.m3u' DirectoryAndM3UFileName = os.path.join(VD['previouslist'], M3UFileName) SavingMediaPath = RelativeIfProgrammeSubpathElseAbsolutePath( self.DirectoryAndFile, RelativeTo = VD['previouslist'], DirectoryOrFile = 'file') WriteToM3UFile( DirectoryAndM3UFileName, MediaLocation = SavingMediaPath, MediaURL = self.MediaURL) def DownloadOnly(MediaURL, MediaPreference): FileName = '' MediaDomain = ParseDomainName(MediaURL) print('Downloading media. Will auto-exit once download completed.') DownloadCommand = ' '.join(( YoutubeDL, "--no-playlist", AdditionalYoutubeDLArguments, MediaPreference, MediaURL)) DownloadCommand = shlex.split(DownloadCommand) DownloadMedia = subprocess.Popen( DownloadCommand, cwd = VD['offlinemedia'], stdout = subprocess.PIPE, universal_newlines = True) while DownloadMedia.poll() is None: Output = DownloadMedia.stdout.readline() print(Output.strip('\n'), end = '', flush = True) #do not end with '\n' print('\r', end = '') #backspace/clear output to print next in same line if Output.startswith('[download] Destination: '): FileName = Output[24:-1] elif Output.startswith('[ffmpeg] Merging formats into "'): FileName = Output[31:-2] elif Output.endswith(' has already been downloaded and merged\n'): FileName = Output[11:-40] elif Output.endswith(' has already been downloaded\n'): FileName = Output[11:-29] MediaTitleUpto = FileName.rfind('-') MediaTitle = FileName[:MediaTitleUpto] DirectoryAndFile = os.path.join(VD['offlinemedia'], FileName) M3UFileName = MediaDomain + ' → ' + MediaTitle + '.m3u' DirectoryAndM3UFileName = os.path.join(VD['previouslist'], M3UFileName) DownloadedMediaPath = RelativeIfProgrammeSubpathElseAbsolutePath( DirectoryAndFile, RelativeTo = VD['previouslist'], DirectoryOrFile = 'file') WriteToM3UFile( DirectoryAndM3UFileName, MediaLocation = DownloadedMediaPath, MediaURL = MediaURL) print('Download complete') def WatchLater(MediaURL): MediaDomain = ParseDomainName(MediaURL) ExtractTitleCommand = ' '.join(( YoutubeDL, "--get-title --no-playlist", AdditionalYoutubeDLArguments, MediaURL)) ExtractedTitle = RunSubprocessAndReturnOutputString( ExtractTitleCommand).strip('\n') if len(ExtractedTitle) > 0: MediaTitle = ExtractedTitle M3UFileName = MediaDomain + ' → ' + MediaTitle + '.m3u' else: MediaTitle = MediaURL M3UFileName = MediaTitle+'.m3u' for Symbols in range(len(VD['ReplaceTheseSymbols'])): M3UFileName = M3UFileName.replace( VD['ReplaceTheseSymbols'][Symbols], VD['ReplaceWithSymbols'][Symbols]) DirectoryAndM3UFileName = os.path.join(VD['watchlaterlist'], M3UFileName) WatchLaterComment = "#You have marked this media to watch later" WriteToM3UFile( DirectoryAndM3UFileName, MediaTitle = MediaTitle, MediaLocation = WatchLaterComment, MediaURL = MediaURL) DirectoryAndM3UFileName2 = VD['watchlaterlist2'] + M3UFileName WriteToM3UFile( DirectoryAndM3UFileName2, MediaTitle = MediaTitle, MediaLocation = WatchLaterComment, MediaURL = MediaURL) print('Saved url to watch later list') def WriteToM3UFile( FilePathAndName, MediaDuration = '', MediaTitle = '', MediaLocation = '', AddtionalText = '', MediaURL = ''): if len(MediaTitle) > 0: Start = '#EXTINF:' LineBreak = '\n' else: Start = '' LineBreak = '' if len(MediaDuration) > 0: MediaDuration = MediaDuration + ',' if len(AddtionalText) > 0: AddtionalText = AddtionalText + '\n' WriteContent = ( Start + MediaDuration + MediaTitle + LineBreak + MediaLocation + '\n' + AddtionalText + '#Source: ' + MediaURL) WriteGeneralTextFiles(WriteContent, FilePathAndName) def RelativeIfProgrammeSubpathElseAbsolutePath( Path, RelativeTo = None, DirectoryOrFile = 'Directory'): Path = os.path.realpath(Path) if Path.startswith(ProgrammeDirectory) == False: return Path if RelativeTo == None: RelativeTo = ProgrammeDirectory RelativePath = os.path.relpath(Path, RelativeTo) return RelativePath def PlayMedia( FileLocation, SeprateAudio = False, AudioSource = None, WorkaroundForVLC = None, VLCWorkAroundTitle = None): # THIS FUNCTION IS A MESS, FIX IT. if SeprateAudio == True and AudioSource == None: M3UFileText = ReadlinesTextFile(FileLocation) AudioSource = GetTextAfter('#Audio:', M3UFileText).strip(' ') QuotedMediaPlayer = DoubleQuoteString(MediaPlayer) PlayCommand = (MediaPlayerOptions + ' ' + FileLocation).strip(' ') if SeprateAudio == True and AudioSource != '': MultipleSourcePlayCommand = \ MediaPlayerMultipleSourcePlayCommand if len(MultipleSourcePlayCommand) > 0: if WorkaroundForVLC != None: MultipleSourcePlayCommand = \ MultipleSourcePlayCommand.replace( 'videosource', WorkaroundForVLC) if VLCWorkAroundTitle != None: MediaTitle = DoubleQuoteString(VLCWorkAroundTitle) MultipleSourcePlayCommand += ' :meta-title=' + MediaTitle else: MultipleSourcePlayCommand = \ MultipleSourcePlayCommand.replace( 'videosource', FileLocation) MultipleSourcePlayCommand = \ MultipleSourcePlayCommand.replace( 'audiosource', AudioSource) PlayCommand = ( MediaPlayerOptions + ' ' + MultipleSourcePlayCommand) else: print( "Error: Have to play seprate video+audio source but " "'MediaPlayerMultipleSourcePlayCommand' is empty. " "This is an unexpected programming bug. Please report " "issue to IMM. Press return key to quit programme") input() sys.exit() ExecuteMediaPlayerCommand = QuotedMediaPlayer + ' ' + PlayCommand print('Now playing in media player') #GnomeMPVExp(ExecuteMediaPlayerCommand) #print("Gnome-MPV") #input() PlayingMedia = subprocess.Popen(ExecuteMediaPlayerCommand, shell = True) """ Below method of stdout=subprocess.PIPE and readlines() is causing perfomance issues. """ #PlayingMedia = subprocess.Popen(ExecuteMediaPlayerCommand, stderr = subprocess.PIPE, stdout = subprocess.PIPE, shell = True) while PlayingMedia.poll() is None: #PlayingMedia.stdout.readline() #Flushing by reading. Maybe otherwise output will fill the memory? #PlayingMedia.stderr.readline() time.sleep(1/60) #if sys.platform == 'win32': #subprocess.Popen(ExecuteMediaPlayerCommand, shell = True) #else: #os.system(ExecuteMediaPlayerCommand) #find solution to it like communicate() or wait()? class StartProcessing: ProcessNextURL = True def __init__(self): for i in range(len(Arguments)): if Arguments[i].startswith('-'): if Arguments[i] == '-s' or Arguments[i] == '--playandsave': self.Do('playandsave') return elif Arguments[i] == '-p' or Arguments[i] == '--play': self.Do('play') return elif Arguments[i] == '-d' or Arguments[i] == '--downloadonly': self.Do('downloadonly') return elif Arguments[i] == '-w' or Arguments[i] == '--watchlater': self.Do('watchlater') return DefaultChoice = VD['defaultchoice'] if (DefaultChoice == 'playandsave' or DefaultChoice == 'play' or DefaultChoice == 'downloadonly' or DefaultChoice == 'watchlater'): self.Do(DefaultChoice) else: print('Type code number of one of the options and press enter\n' '1 - play media in desktop media player\n' '2 - play media in desktop media player and save as well to drive\n' '3 - only download media (don\'t play in desktop media player)\n' '4 - save url in watch later list') while True: UserInput = input() if UserInput == '1': self.Do('play') break elif UserInput == '2': self.Do('playandsave') break elif UserInput == '3': self.Do('downloadonly') break elif UserInput == '4': self.Do('watchlater') break print('Invalid option, Try Again') """ Do what? Play, PlayAndSave, DownloadOnly, WatchLater """ def Do(self, What): global MediaURLListString, MediaURLList, ClassMediaPreference MediaURLList = FindMediaURLs().MediaURLs MediaURLListString = ListIntoString( MediaURLList, QuoteItems = 2) if What == 'play': ClassMediaPreference = CalculateMediaQualityPreference() QueueInIMM = VD['queueinimm'] for i in range(len(MediaURLList)): if QueueInIMM == 'yes': print('Press return key to process next media url') input() MediaURL = MediaURLList[i] if self.DirectlyPlayIfAvailableOffline(MediaURL) == True: continue MediaPreference = \ ClassMediaPreference.ManuallySelectFormat(MediaURL) print('Processing ' + MediaURL) threading.Thread( target = PlayOnly, args = (MediaURL, MediaPreference)).start() self.WaitForProcessToComplete() continue #is this required or will work correctly? elif What == 'playandsave': global PlayingMediaNumber ClassMediaPreference = CalculateMediaQualityPreference() PlayingMediaNumber = 0 for i in range(len(MediaURLList)): MediaURL = MediaURLList[i] if self.DirectlyPlayIfAvailableOffline(MediaURL) == True: continue MediaPreference =\ ClassMediaPreference.ManuallySelectFormat(MediaURL) print('Processing ' + MediaURL) threading.Thread( target = PlayAndSave, args = (i, MediaURL, MediaPreference)).start() self.WaitForProcessToComplete() continue #is this required or will work correctly? elif What == 'downloadonly': ClassMediaPreference = \ CalculateMediaQualityPreference(DownloadOnly = True) ApplyORNotMediaPreference = \ VD['applymediapreferencetodownloadonlyalso'] for i in range(len(MediaURLList)): MediaURL = MediaURLList[i] if ApplyORNotMediaPreference == 'yes': MediaPreference = \ ClassMediaPreference.ManuallySelectFormat(MediaURL) if MediaPreference == 'Failed': return else: MediaPreference = '' print('Processing ' + MediaURL) DownloadOnly(MediaURL, MediaPreference) elif What == 'watchlater': for i in range(len(MediaURLList)): MediaURL = MediaURLList[i] print('Processing ' + MediaURL) WatchLater(MediaURL) def WaitForProcessToComplete(self): StartProcessing.ProcessNextURL = False while StartProcessing.ProcessNextURL == False: time.sleep(1) def DirectlyPlayIfAvailableOffline(self, MediaURL): OfflineProtocol = 'Offline://' LenOfflineProtocol = len(OfflineProtocol) OfflineFile = MediaURL.strip(Quotes) if OfflineFile.startswith(OfflineProtocol): OfflineFile = DoubleQuoteString( OfflineFile[LenOfflineProtocol:]) PlayMedia(OfflineFile) return True return False class ProgrammeArguments: def __init__(self): self.GetArguments() self.SplitOneLetterArguments() def GetArguments(self): global Arguments if len(sys.argv) > 1: Arguments = sys.argv[1:] else: Arguments = [] """ Can below function be written in a better way? """ def SplitOneLetterArguments(self): ArgumentsHavingValues = ['c'] ArgumentsAndValuesDictionary = {} ItemNumberOfValue = 1 DeleteList = [] for i in range(len(Arguments)): if Arguments[i].startswith('-') == True: if Arguments[i].startswith('--') == False: if len(Arguments[i]) > 2: #eg. len(-p) == 2, len(-pc) == 3 if Arguments[i].find('=') == -1: #eg. -c=xyz SplitString = Arguments[i][1:] for s in range(len(SplitString)): NewItem = '-' + SplitString[s] NewItemValue = '' if NewItem[1:] in ArgumentsHavingValues: NewItemValueNumber = i + ItemNumberOfValue ItemNumberOfValue += 1 # index out of range risk NewItemValue = Arguments[NewItemValueNumber] DeleteList.append(NewItemValueNumber) ArgumentsAndValuesDictionary[NewItem] = NewItemValue DeleteList.append(i) DeleteList = sorted(DeleteList) for i in range(len(DeleteList)): """ Since an i is deleted from list, next to be deleted will be itemtobedeleted - 1. for next it will be itemtobedeleted - 2 etc. """ del Arguments[DeleteList[i] - i] for keys, values in ArgumentsAndValuesDictionary.items(): Arguments.append(keys) if len(values) > 0: Arguments.append(values) class RunInTerminal: """ Reexecute programme in terminal with received arguments if not already running in terminal. """ def __init__(self): if sys.stdout.isatty() == True: return else: #find command for osx (Mac) SupportedPlatorm = ( 'linux', 'freebsd', 'openbsd', 'netbsd', 'trueos', 'darwin') if sys.platform.startswith(SupportedPlatorm): Arguments2 = '' for i in range(len(Arguments)): Argument = Arguments[i] if Argument.startswith('-') == False: Arguments2 += ' ' + DoubleQuoteString(Argument) else: Arguments2 += ' ' + Argument Arguments2=Arguments2.replace('\'','\'\\\'\'') """ Programme = os.path.dirname(os.path.realpath( sys.argv[0])) + sys.argv[0][1:] """ Programme = sys.argv[0] Programme = DoubleQuoteString(Programme) ProgrammeAndArguments = SingleQuoteString( Programme + Arguments2) TerminalEmulator = self.FindTerminalEmulator() if TerminalEmulator == None: sys.exit() ReexecuteCommand = ( TerminalEmulator + ' ' + ProgrammeAndArguments) os.system(ReexecuteCommand) sys.exit() def FindTerminalEmulator(self): SpecifiedTerminal = VD['terminalemulator'] """ Likely bug - execution command for different terminals would be different like -e or something else. Should have a tuple of execution command as well and return Terminal Emulator including execution command. """ TerminalEmulators = ( SpecifiedTerminal, 'x-terminal-emulator', 'gnome-terminal', 'konsole', 'konsole5', 'mate-terminal', 'xfce4-terminal', 'terminal', 'qterminal', 'lxterminal', 'roxterm', 'vt', 'vte', 'vte3', 'tilix', 'terminology', 'guake', 'anyterm', 'eterm', 'yakuake', 'fbterm', 'shellinabox', 'sakura', 'xterm', 'xterm-256color', 'uxterm', 'vt52', 'vt100', 'vt102', 'vt220', 'ansi', 'dumb', 'qodem', 'termit', 'x3270-text', 'mrxvt', 'rxvt', 'x3270-x11', 'x3270', 'wterm', 'rxvt-unicode ', 'rxvt-unicode-256color', 'aterm', 'terminator', 'gtk30', 'tilda', 'yakuake', 'urxvt', 'roxterm', 'evilvte', 'sakura', 'i3', 'tmux', 'xcompmgr', 'yeahconsole', 'stjerm', 'dwm', 'sterm', 'altyo', 'mlterm', 'koi8rxterm', 'lxterm') for i in range(len(TerminalEmulators)): TerminalPath = FindFile(TerminalEmulators[i]) if TerminalPath != None: TerminalPath = DoubleQuoteString(TerminalPath) return TerminalPath + ' -e' if sys.platform == 'darwin': return 'terminal -e' def GetPathsList(): FindPaths = 'echo $PATH' PathsString = RunSubprocessAndReturnOutputString( FindPaths, Shell = True) PathsList = PathsString.split(Configuration.PathListSeprator) return PathsList def FindFile(FileNameOrPath, ReturnFullPath=True): """ Find if specified file like media player exist in $PATH or refering to a file that exist. NOTE: should add current directory as well? """ if FileNameOrPath == None: #temporary fix return FileNameOrPath = FileNameOrPath.strip(Quotes) PathsList = GetPathsList() for i in range(len(PathsList)): FilePath = os.path.join(PathsList[i], FileNameOrPath) FileFound = os.path.isfile(FilePath) if FileFound == True: if ReturnFullPath == True: return FilePath return FileNameOrPath FileFound = os.path.isfile(FileNameOrPath) if FileFound == True: if ReturnFullPath == True: return os.path.abspath(FileNameOrPath) return FileNameOrPath def RunSubprocessAndReturnOutputString(RunCommand, Shell=False): if Shell==False: RunCommand=shlex.split(RunCommand) Execute=subprocess.Popen( RunCommand, stdout=subprocess.PIPE, shell=Shell) Byte=Execute.stdout.read() String=Byte.decode(Encoding) return String class Configuration: """ Configure variables used in programme based on OS, configuration file, programme arguments, working directory etc. """ global Quotes, PathSlash, FileProtocol Quotes = '"\'' if sys.platform == 'win32': PathSlash = '\\' PathListSeprator = ';' UserConfigurationDirectory = ( 'AppData' + PathSlash + 'Roaming' + PathSlash) else: PathSlash = '/' PathListSeprator = ':' UserConfigurationDirectory = '.config' + PathSlash FileProtocol = 'file:' + 2*PathSlash OneDirectoryUp = '..' + PathSlash def __init__(self): global Encoding self.FindVariousDirectories() os.chdir(ProgrammeDirectory) self.ReadConfigurationFile() self.GenerateVariablesDictionary() self.GlobalVariables() self.ModifyCheckCreateDirectories() """ This might be the problem in windows. it maybe decoding using codepage instead of unicode. Try as utf-8 and printing binary. if have crazy characters or numbers than solved the problem. """ Encoding = sys.stdout.encoding def FindVariousDirectories(self): global\ ProgrammeDirectory,\ StartedWorkingDirectory,\ HomeDirectory ProgrammeDirectory = os.path.dirname( os.path.realpath(sys.argv[0])) StartedWorkingDirectory = os.getcwd() HomeDirectory = str(pathlib.Path.home()) def ReadConfigurationFile(self): global ConfigurationFileTextLines, ConfigurationFilePath SpecifiedConfigurationFilePath = '' ConfigurationFileName = "imm.conf" for i in range(len(Arguments)): if (Arguments[i].startswith("-c=") or Arguments[i].startswith("--configfile=") or Arguments[i].startswith("--configurationfilepath=")): Arg = Arguments[i] FilePathStartsAt = Arg.find('=') SpecifiedConfigurationFilePath = Arg[FilePathStartsAt + 1:] ConfigurationPaths = { "SpecifiedConfigurationFilePath " : SpecifiedConfigurationFilePath, "InProgrammeDirectory" : os.path.join( ProgrammeDirectory, ConfigurationFileName), "InHomeDirectory" : HomeDirectory + PathSlash + Configuration.UserConfigurationDirectory + "imm" + PathSlash + ConfigurationFileName} for Keys, Paths in ConfigurationPaths.items(): Path = os.path.abspath(Paths) if os.path.isfile(Path): ConfigurationFilePath = Path ConfigurationFileTextLines = ReadlinesTextFile(Path) return DefaultConfigurationTextLines = [] ConfigurationFileTextLines = DefaultConfigurationTextLines ConfigurationFilePath = \ ConfigurationPaths["InProgrammeDirectory"] def EditConfigurationFile(self, Variable, Value): global ConfigurationFileTextLines ReplaceWith = Variable + '=' + Value Edited = False for Lines in range(len(ConfigurationFileTextLines)): Line = ConfigurationFileTextLines[Lines] VariableInLineAt = Line.find('=') VariableInLine = Line[:VariableInLineAt].strip(' ') if VariableInLine == Variable: ConfigurationFileTextLines[Lines] = ReplaceWith Edited = True if Edited == False: ConfigurationFileTextLines.append(ReplaceWith) WriteGeneralTextFiles( ConfigurationFileTextLines, ConfigurationFilePath) def GenerateVariablesDictionary(self): global VD #VD is VariablesDictionary """ These variables can be modified via arguments and configuration file. unmodifiable variables are added/overwritten after processing configuration file and arguments. """ VD = { 'mediaplayeroptions' : '', 'alternatemediaplayeroptions' : '', "mediaplayermultiplesourcesplaycommand" : '', "alternatemediaplayermultiplesourcesplaycommand" : '', 'queueinimm' : 'no', 'appendmedialist' : 'no', 'defaultchoice' : 'ask', 'allowseprateaudiosource' : 'yes', # Do not use internet speed if not specified. 'internetspeed':None, # By default accept upto 4K resolution 'mediaqualityheight':2200, 'applymediapreferencetodownloadonlyalso' : 'no', 'manuallyselectformat' : 'no', 'preferaudioonly' : 'no', 'customformatoptions' : '', 'additionalyoutube-dlarguments' : '', 'previouslist' : 'Previous', 'watchlaterlist' : 'Watch Later', 'offlinemedia' : 'Offline', 'useterminal' : '', 'youtube-dl' : './Code/youtube-dl'} self.UpdateVariablesDictionaryFromConfigurationFile( ConfigurationFileTextLines) #RequiredVariables = ('mediaplayer') ExpandOneLetterArguments = { # 'm' -> play with alternate media player or media player # specified in argument. 'm' : 'mediaplayer', 'a' : 'preferaudioonly'} #Use default varibales value if value not specified in argument DefaultArgumentVariablesValue = { 'preferaudioonly' : 'yes', 'mediaplayer' : VD.get('alternatemediaplayer')} """ shouldn't both be considered: --variable=value & --variable value currently considering only former. Fix it. if i < len(Arguments): if (Argument[i+1].startswith('-') == False and Argument[i+1].strip(Quotes).casefold().startswith(VD['validmediaurlstart']) == False and (Argument[i+1].strip(Quotes).casefold().startswith(FileProtocol) == False): value = Argument[i+1] """ for i in range(len(Arguments)): if Arguments[i].startswith('-'): Arg = Arguments[i] Seprator = Arg.find('=') if Seprator != -1: Variable = Arg[:Seprator].strip('-') if Variable in ExpandOneLetterArguments: Variable = ExpandOneLetterArguments[Variable] Value = Arg[Seprator + 1:] else: Variable = Arg.strip('-') if Variable in ExpandOneLetterArguments: Variable = ExpandOneLetterArguments[Variable] if Variable in DefaultArgumentVariablesValue: Value = DefaultArgumentVariablesValue[Variable] else: Value = 'yes' VD[Variable] = Value UnmodifiableVD = { # Should instead do if str.lowercase() == VD[key][i]: 'validmediaurlstart' : ("http://", "https://"), 'validextensioncontaningmediaurl1' : (".m3u", ".m3u8"), # Find and replace possible conlicting symbols, atleast for # now, later either use youtube-dl without shell = True or # specify ASCII only characters in filenames to youtube-dl. # remove this when fix unicode issue fixed in windows but # if os=win32, specify *safe* characters only in youtube-dl 'ReplaceTheseSymbols' : ('?', ' : ', '"', '/', '\\', '*', '<', '>', '|'), 'ReplaceWithSymbols' : ('¿', ';', '\'', '-', '-', '★', '{', '}', 'I')} VD.update(UnmodifiableVD) def UpdateVariablesDictionaryFromConfigurationFile( self, ConfigurationFileTextLines): VariableValueSeprator = '=' for Lines in range(len(ConfigurationFileTextLines)): Line = ConfigurationFileTextLines[Lines] if len(Line) == 0 or Line.startswith('#') or Line.startswith('['): continue SepratorAt = Line.find(VariableValueSeprator) if SepratorAt != -1: VariableName = Line[:SepratorAt].strip(' ') VariableName = StandardVariableName(VariableName) VariableValue = Line[SepratorAt+1:].strip(' ') if len(VariableName) > 0 and len(VariableValue) > 0: VD[VariableName] = VariableValue def GlobalVariables(self): """ Some global vairbales from variablesdictionary for neatness. """ global AdditionalYoutubeDLArguments AdditionalYoutubeDLArguments = \ VD['additionalyoutube-dlarguments'] def ModifyCheckCreateDirectories(self): self.ChangePathSlashesIfWindowsOS() self.SetM3UFileAndOfflineMediaDirectory() Directories = ( 'offlinemedia', 'previouslist', 'watchlaterlist', 'previouslist2', 'watchlaterlist2') for i in range(len(Directories)): VD[Directories[i]] =( os.path.abspath(VD[Directories[i]]) + PathSlash) DirectoryUse = ( 'save media file(s) to this directory', 'save media history to this directory', 'save watch later list to this directory', 'save media history to this directory', 'save watch later list to this directory') self.CreateDirectories( Directories, DirectoryUse) def ChangePathSlashesIfWindowsOS(self): if sys.platform == 'win32': ModifyDirectories = ( 'previouslist', 'watchlaterlist', 'offlinemedia') for i in range(len(ModifyDirectories)): VD[ModifyDirectories[i]] = ( VD[ModifyDirectories[i]].replace('/', '\\')) def SetM3UFileAndOfflineMediaDirectory(self): """ Use StartedWorkingDirectory for saving/downloading media execept when same as HomeDirectory or sub directory of ProgrammeDirectory than use ProgrammeDirectory. """ if (StartedWorkingDirectory.startswith(ProgrammeDirectory) or StartedWorkingDirectory == HomeDirectory): VD['previouslist2'] = VD['previouslist'] VD['watchlaterlist2'] = VD['watchlaterlist'] else: SetToStartedWorkingDirectory = ( 'previouslist2', 'watchlaterlist2', 'offlinemedia') for i in range(len(SetToStartedWorkingDirectory)): VD[SetToStartedWorkingDirectory[i]] = ( StartedWorkingDirectory) def CreateDirectories( self, Directories, DirectoryUse = ('unknown')): """ Directories which are in VD only. Not a generic function but specific .""" for i in range(len(Directories)): if os.path.isdir(VD[Directories[i]]) == False: NewDirectory = VD[Directories[i]] if NewDirectory.startswith( ProgrammeDirectory) == True: os.mkdir(NewDirectory) else: print( Directories[i] + ' directory "' + NewDirectory + '" does not exist. Type \'yes\' to create\ this directory and ' + DirectoryUse[i] + '. Or simply press return key to exit.') while True: CreateOrNot = input() if CreateOrNot == 'yes': try: os.mkdir(NewDirectory) break except: print( "Could not create directory. " "Probably permission error. Still " "do not run as root/sudo/" "administrator. Instead try some " "other directory. Press return to " "exit.") input() sys.exit() if CreateOrNot == '': print('Goodbye!') time.sleep(3) sys.exit() else: print( "Invalid input. Type either \'yes\' " "or nothing.") def MediaPlayerVariables(self): global\ MediaPlayer,\ MediaPlayerOptions,\ MediaPlayerMultipleSourcePlayCommand if '--alternatemediaplayer' in Arguments or '-m' in Arguments: MediaPlayer = LookupInVD('alternatemediaplayer') if MediaPlayer != None: MediaPlayerOptions = VD['alternatemediaplayeroptions'] MediaPlayerMultipleSourcePlayCommand = \ VD["alternatemediaplayermultiplesourcesplaycommand"] return else: print( "Alternate media player not found in configuration file" " or arguments. Trying to use primary media player.") MediaPlayer = self.LookupInVD("mediaplayer") if MediaPlayer != None: MediaPlayerOptions = VD["mediaplayeroptions"] MediaPlayerMultipleSourcePlayCommand = \ VD["mediaplayermultiplesourcesplaycommand"] else: MediaPlayer = self.FindExecutable("Media Player") MediaPlayerOptions = '' self.EditConfigurationFile( "mediaplayeroptions", MediaPlayerOptions) MediaPlayerMultipleSourcePlayCommand =\ self.LookInPreConfiguredCmdOfMediaPLayers() self.EditConfigurationFile( "mediaplayermultiplesourcesplaycommand", MediaPlayerMultipleSourcePlayCommand) def LookInPreConfiguredCmdOfMediaPLayers(self): CheckMediaPlayer = MediaPlayer PreConfiguredCmd = { "vlc" : "videosource :input-slave=audiosource", "mpv" : "videosource --audio-file=audiosource"} #Execeptions = ("gnome-mpv", "umpv") DoesNotSupportWarning = ( "Media player may not support seprate audio source " "playback. Seprate video+audio formats will not be " "considered. If you know that specified media player " "supports seprate audio source than add that coommand to " "imm.conf file as:\n" "\n" "mediaplayermultiplesourcesplaycommand=" "videosource --audio audiosource\n" "\n" "videosource and audiosource will be replaced with " "video url/file and audio url/file respectively.") CheckMediaPlayer = os.path.split(MediaPlayer)[1] if CheckMediaPlayer.endswith(".exe"): CheckMediaPlayer = CheckMediaPlayer[:-4] MediaPlayerCmd = PreConfiguredCmd.get(CheckMediaPlayer) if MediaPlayerCmd == None: print(DoesNotSupportWarning) return '' else: return MediaPlayerCmd #for Player, Cmd in PreConfiguredCmd.items(): #if CheckMediaPlayer.endswith(Player): #if MediaPlayer.endswith(Execeptions): #print(DoesNotSupportWarning) #return '' #else: #return Cmd #else: #print(DoesNotSupportWarning) #return '' def FindExecutable(self, ExecName): SampleExec = StandardVariableName(ExecName) Executable = self.LookupInVD(SampleExec) if Executable != None: return Executable print( ExecName + " not found in configuration nor specified in " "arguments. Enter " + ExecName + " run command (if any) " "like '" + SampleExec + "' or full path to executable like" " \"/usr/bin/" + SampleExec + "\" in GNU/Linux and other " "unix-like OS or \"c:\\Programme files\\" + ExecName + "\\" + SampleExec + ".exe\" in Windows") while True: Executable = input() Executable = FindFile(Executable) if Executable == None: print( ExecName + " not found, try giving full path or " "try again") else: self.EditConfigurationFile(SampleExec, Executable) break return Executable def LookupInVD(self, VDKey): Executable = VD.get(VDKey) return FindFile(Executable) #return None if not found def Main(): global MediaPlayer, YoutubeDL ProgrammeArguments() Config = Configuration() RunInTerminal() YoutubeDL = Config.FindExecutable("Youtube-DL") Config.MediaPlayerVariables() StartProcessing() def GnomeMPVExp(PlayCommand): ConfigFile = HomeDirectory+"/.config/mpv/mpv.conf" ConfigList = ReadlinesTextFile(ConfigFile) ConfigListBak = tuple(ConfigList) print(PlayCommand) PlayCommand = shlex.split(PlayCommand)[1:] print(PlayCommand) input() for i in range(len(PlayCommand)): ConfigList.append(PlayCommand[i]) WriteGeneralTextFiles(ConfigList, ConfigFile) time.sleep(5) os.system("gnome-mpv") time.sleep(50) WriteGeneralTextFiles(ConfigListBak, ConfigFile) #================================== Main()