You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1642 lines
65 KiB
Python

#!/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 <https://www.gnu.org/licenses/>.
"""
"""
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()