prolixab commited on
Commit
ad31616
0 Parent(s):

Initial commit

Browse files
.gitattributes ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
.gitignore ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # poetry
98
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102
+ #poetry.lock
103
+
104
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
105
+ __pypackages__/
106
+
107
+ # Celery stuff
108
+ celerybeat-schedule
109
+ celerybeat.pid
110
+
111
+ # SageMath parsed files
112
+ *.sage.py
113
+
114
+ # Environments
115
+ .env
116
+ .venv
117
+ env/
118
+ venv/
119
+ ENV/
120
+ env.bak/
121
+ venv.bak/
122
+
123
+ # Spyder project settings
124
+ .spyderproject
125
+ .spyproject
126
+
127
+ # Rope project settings
128
+ .ropeproject
129
+
130
+ # mkdocs documentation
131
+ /site
132
+
133
+ # mypy
134
+ .mypy_cache/
135
+ .dmypy.json
136
+ dmypy.json
137
+
138
+ # Pyre type checker
139
+ .pyre/
140
+
141
+ # pytype static type analyzer
142
+ .pytype/
143
+
144
+ # Cython debug symbols
145
+ cython_debug/
146
+
147
+ # PyCharm
148
+ # JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
149
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
150
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
151
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
152
+ #.idea/
153
+
154
+ audio
155
+ image
156
+ music
157
+ video
.idea/.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Default ignored files
2
+ /shelf/
3
+ /workspace.xml
.idea/batcher.iml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <module type="PYTHON_MODULE" version="4">
3
+ <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <excludeFolder url="file://$MODULE_DIR$/venv" />
6
+ </content>
7
+ <orderEntry type="jdk" jdkName="Python 3.12 (filmr)" jdkType="Python SDK" />
8
+ <orderEntry type="sourceFolder" forTests="false" />
9
+ </component>
10
+ </module>
.idea/inspectionProfiles/profiles_settings.xml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <component name="InspectionProjectProfileManager">
2
+ <settings>
3
+ <option name="USE_PROJECT_PROFILE" value="false" />
4
+ <version value="1.0" />
5
+ </settings>
6
+ </component>
.idea/misc.xml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="Black">
4
+ <option name="sdkName" value="Python 3.12 (batcher)" />
5
+ </component>
6
+ <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (filmr)" project-jdk-type="Python SDK" />
7
+ <component name="PyCharmProfessionalAdvertiser">
8
+ <option name="shown" value="true" />
9
+ </component>
10
+ </project>
.idea/modules.xml ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/batcher.iml" filepath="$PROJECT_DIR$/.idea/batcher.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="" vcs="Git" />
5
+ </component>
6
+ </project>
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 prolixab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md ADDED
@@ -0,0 +1 @@
 
 
1
+ # Filmr
client_secrets.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "web": {
3
+ "client_id": "[[INSERT CLIENT ID HERE]]",
4
+ "client_secret": "[[INSERT CLIENT SECRET HERE]]",
5
+ "redirect_uris": [],
6
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
7
+ "token_uri": "https://accounts.google.com/o/oauth2/token"
8
+ }
9
+ }
demux/gradio-gradio.txt ADDED
File without changes
demux/utbildning-dystopic.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ file D:\\repos\\filmr\\video\\utbildning-dystopic-0.avi
2
+ file D:\\repos\\filmr\\video\\utbildning-dystopic-1.avi
3
+ file D:\\repos\\filmr\\video\\utbildning-dystopic-2.avi
4
+ file D:\\repos\\filmr\\video\\utbildning-dystopic-3.avi
5
+ file D:\\repos\\filmr\\video\\utbildning-dystopic-4.avi
demux/utbildning-utopic.txt ADDED
File without changes
ffmpegmaker.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ffmpeg
2
+ import os
3
+ import moviepy
4
+ from moviepy.editor import VideoFileClip, TextClip, CompositeVideoClip, vfx, afx
5
+ from moviepy.video.compositing.concatenate import concatenate_videoclips
6
+
7
+
8
+ def make_film(filelist, path, filename):
9
+ output_file = os.path.join(path, filename)
10
+ ffmpeg.input(filename=filelist, f='concat', safe='0').output(output_file).run()
11
+
12
+
13
+ def make_title(intro_filmclip):
14
+ txt_title = (TextClip("15th century dancing\n(hypothetical)", fontsize=70,
15
+ font="Century-Schoolbook-Roman", color="white")
16
+ .margin(top=15, opacity=0)
17
+ .set_position(("center", "center")))
18
+
19
+ title = (CompositeVideoClip([intro_filmclip, txt_title])
20
+ .fadein(.5)
21
+ .set_duration(intro_filmclip.duration))
22
+
23
+ return title
24
+
25
+
26
+ def make_credits(title):
27
+ txt_credits = """
28
+ CREDITS
29
+
30
+ Video excerpt: Le combat en armure au XVe siècle
31
+ By J. Donzé, D. Jaquet, T. Schmuziger,
32
+ Université de Genève, Musée National de Moyen Age
33
+
34
+ Music: "Frontier", by DOCTOR VOX
35
+ Under licence Creative Commons
36
+ https://www.youtube.com/user/DOCTORVOXofficial
37
+
38
+ Video editing © Zulko 2014
39
+ Licence Creative Commons (CC BY 4.0)
40
+ Edited with MoviePy: http://zulko.github.io/moviepy/
41
+ """
42
+
43
+ credits = (TextClip(txt_credits, color='white',
44
+ font="Century-Schoolbook-Roman", fontsize=35, kerning=-2,
45
+ interline=-1, bg_color='black', size=title.size)
46
+ .set_duration(2.5)
47
+ .fadein(.5)
48
+ .fadeout(.5))
49
+
50
+ return credits
51
+
52
+
53
+ def make_short_film(path, img, audio, filename, i):
54
+ output_file = os.path.join(path, filename)
55
+ input_img = ffmpeg.input(img)
56
+ input_audio = ffmpeg.input(audio)
57
+ final_filename = f'{output_file}-{i}.avi'
58
+ output = ffmpeg.concat(input_img, input_audio, v=1, a=1).output(final_filename).overwrite_output()
59
+ ffmpeg.run(output)
60
+ return final_filename
61
+
62
+
63
+ def make_short_films(image_file_list, audio_file_list, video_path, filename):
64
+ video_list = []
65
+ for i, img in enumerate(image_file_list):
66
+ final_filename = make_short_film(video_path, img, audio_file_list[i], filename, i)
67
+ video_list.append(final_filename)
68
+ return video_list
69
+
70
+
71
+ def summarize_film(file_list, path, filename):
72
+ output_file = f'{os.path.join(path, filename)}.mp4'
73
+ video_clips = []
74
+ intro = VideoFileClip(file_list[0])
75
+ intro_clip = make_title(intro)
76
+ video_clips.append(intro_clip)
77
+ for s in file_list[1::]:
78
+ video_clips.append(VideoFileClip(s).fx(vfx.fadein,1).fx(vfx.fadeout,1))
79
+ video_clips.append(make_credits(video_clips[0]))
80
+ final = concatenate_videoclips(video_clips)
81
+
82
+ # Write output to the file
83
+ final.write_videofile(output_file)
84
+ return output_file
85
+ # (
86
+ # ffmpeg
87
+ # .concat(t)
88
+ # .output(f'output-video.mp4')
89
+ # .overwrite_output()
90
+ # .run()
91
+ # )
filmr.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from pathlib import Path, PurePath, PurePosixPath, PureWindowsPath
4
+ import ffmpegmaker as film
5
+ import ffmpeg
6
+ import speech
7
+ import script
8
+ import image
9
+ from prompts import system_prompt, image_style_prompt
10
+
11
+ # #topic_array = ["utbildning", "transport", "ai", "energiförsörjning", "projekt Planet B", "bostäder", "mat"]
12
+ topic_array = ["utbildning"]
13
+ #topia_array = ["dystopic", "utopic"]
14
+ topia_array = ["dystopic"]
15
+
16
+ #File paths
17
+ parent_dir = os.getcwd()
18
+ base_audio_path = os.path.join(parent_dir, "audio")
19
+ base_image_path = os.path.join(parent_dir, "image")
20
+ base_script_path = os.path.join(parent_dir, "script")
21
+ base_video_path = os.path.join(parent_dir, "video")
22
+ base_demux_path = os.path.join(parent_dir, "demux")
23
+
24
+
25
+ def create_filelist(textfile_path, video_list):
26
+ with open(textfile_path, "w") as f:
27
+ for i, img in enumerate(video_list):
28
+ img_with_double = img.replace('\\', '\\\\')
29
+ f.write(f'file {img_with_double}\n')
30
+
31
+
32
+ def populate_filelists(audio_file_list, image_file_list, audio_file_length_list, topic, topia):
33
+ # Find all the newly created image files
34
+ p = Path(base_image_path)
35
+ for filepath in p.glob(f'{topic}-{topia}-*.png'):
36
+ image_file_list.append(filepath)
37
+ # Find all the newly created sound files
38
+ p = Path(base_audio_path)
39
+ for filepath in p.glob(f'{topic}-{topia}-*.mp3'):
40
+ audio_file_list.append(filepath)
41
+ # Store all the lengths of the newly created files
42
+ for file in audio_file_list:
43
+ duration = ffmpeg.probe(file)["format"]["duration"]
44
+ audio_file_length_list.append(duration)
45
+
46
+
47
+ def create_film(topic, topia):
48
+ audio_file_list = []
49
+ image_file_list = []
50
+ audio_file_length_list = []
51
+ # Create the script
52
+ # created_script = script.create_script(system_prompt, topic, topia)
53
+ # script.save_script(created_script, base_script_path, topic, topia)
54
+ # # Convert the script to JSON
55
+ # data = json.loads(created_script)
56
+ # sequences = data["sekvenser"]
57
+ # # Create all images and audio for each sequence.
58
+ # for i, sequence in enumerate(sequences):
59
+ # filename = f'{topic}-{topia}-{i}'
60
+ # speech.create_speech(sequence["beskrivning"], base_audio_path, filename)
61
+ # image.generate_DALLE_images(sequence["bild"], base_image_path, filename)
62
+
63
+ populate_filelists(audio_file_list, image_file_list, audio_file_length_list, topic, topia)
64
+ # Make short films
65
+ video_list = film.make_short_films(image_file_list, audio_file_list, base_video_path, f'{topic}-{topia}')
66
+ # Construct the demuxer file
67
+ #textfile_path = os.path.join(base_demux_path, f'{topic}-{topia}.txt')
68
+ #create_filelist(textfile_path, video_list)
69
+ # Make final film
70
+ output = film.summarize_film(video_list, base_video_path, f'{topic}-{topia}')
71
+ return output
72
+
73
+
74
+ def create_multiple_films(topic_list, topia_list):
75
+ for topic in topic_list:
76
+ for topia in topia_list:
77
+ create_film(topic, topia)
78
+
79
+
80
+ if __name__ == '__main__':
81
+ create_multiple_films(topic_array, topia_array)
82
+
83
+ # with open(textfile_path, "w") as f:
84
+ # for i, img in enumerate(image_file_list):
85
+ # f.write(f'file {img}\n')
86
+ # duration = math.ceil(float(audio_file_length_list[i]))
87
+ # f.write(f'duration {duration}\n')
gradio_run.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import filmr
3
+
4
+
5
+
6
+
7
+ def greet(topic, topia):
8
+ output = filmr.create_film(topic, topia)
9
+
10
+ return output
11
+
12
+
13
+ filmr_gui = gr.Interface(
14
+ fn=greet,
15
+ inputs=["text", "text"],
16
+ outputs=["download_button"],
17
+ title="Filmr",
18
+ description="Create a film with the help of GPT4",
19
+ )
20
+
21
+ filmr_gui.launch(allowed_paths=["D:\repos\filmr"])
image.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+ DALL-E image generation example for openai>1.2.3, saves requested images as files
3
+ -- not a code utility, has no input or return
4
+
5
+ # example pydantic models returned by client.images.generate(**img_params):
6
+ ## - when called with "response_format": "url":
7
+ images_response = ImagesResponse(created=1699713836, data=[Image(b64_json=None, revised_prompt=None, url='https://oaidalleapiprodscus.blob.core.windows.net/private/org-abcd/user-abcd/img-12345.png?st=2023-11-11T13%3A43%3A56Z&se=2023-11-11T15%3A43%3A56Z&sp=r&sv=2021-08-06&sr=b&rscd=inline&rsct=image/png&skoid=6aaadede-4fb3-4698-a8f6-684d7786b067&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2023-11-10T21%3A41%3A11Z&ske=2023-11-11T21%3A41%3A11Z&sks=b&skv=2021-08-06&sig=%2BUjl3f6Vdz3u0oRSuERKPzPhFRf7qO8RjwSPGsrQ/d8%3D')])
8
+
9
+ requires:
10
+ pip install --upgrade openai
11
+ pip install pillow
12
+ '''
13
+ import os
14
+ from io import BytesIO
15
+ import openai # for handling error types
16
+ from datetime import datetime # for formatting date returned with images
17
+ import base64 # for decoding images if recieved in the reply
18
+ import requests # for downloading images from URLs
19
+ from PIL import Image # pillow, for processing image types
20
+ from prompts import image_style_prompt
21
+ from dotenv import load_dotenv
22
+ load_dotenv()
23
+
24
+ api_key = os.getenv("OPENAI_API_KEY")
25
+
26
+ os.environ["OPENAI_API_KEY"] = api_key
27
+
28
+ def old_package(version, minimum): # Block old openai python libraries before today's
29
+ version_parts = list(map(int, version.split(".")))
30
+ minimum_parts = list(map(int, minimum.split(".")))
31
+ return version_parts < minimum_parts
32
+
33
+ if old_package(openai.__version__, "1.2.3"):
34
+ raise ValueError(f"Error: OpenAI version {openai.__version__}"
35
+ " is less than the minimum version 1.2.3\n\n"
36
+ ">>You should run 'pip install --upgrade openai')")
37
+
38
+ from openai import OpenAI
39
+ # client = OpenAI(api_key="sk-xxxxx") # don't do this, OK?
40
+ client = OpenAI() # will use environment variable "OPENAI_API_KEY"
41
+
42
+
43
+
44
+ def generate_DALLE_images(user_prompt, path, filename):
45
+
46
+ prompt = (
47
+ f"Subject: {user_prompt} " # use the space at end
48
+ f"Style: {image_style_prompt}" # this is implicit line continuation
49
+ )
50
+
51
+ image_params = {
52
+ "model": "dall-e-3", # Defaults to dall-e-2
53
+ "n": 1, # Between 2 and 10 is only for DALL-E 2
54
+ "size": "1024x1024", # 256x256, 512x512 only for DALL-E 2 - not much cheaper
55
+ "prompt": prompt, # DALL-E 3: max 4000 characters, DALL-E 2: max 1000
56
+ "user": "myName", # pass a customer ID to OpenAI for abuse monitoring
57
+ }
58
+
59
+ ## -- You can uncomment the lines below to include these non-default parameters --
60
+
61
+ image_params.update({"response_format": "b64_json"}) # defaults to "url" for separate download
62
+
63
+ ## -- DALL-E 3 exclusive parameters --
64
+ image_params.update({"model": "dall-e-3"}) # Upgrade the model name to dall-e-3
65
+ image_params.update({"size": "1792x1024"}) # 1792x1024 or 1024x1792 available for DALL-E 3
66
+ # image_params.update({"quality": "hd"}) # quality at 2x the price, defaults to "standard"
67
+ # image_params.update({"style": "natural"}) # defaults to "vivid"
68
+
69
+ print(f'Generating image {filename}')
70
+ try:
71
+ images_response = client.images.generate(**image_params)
72
+ except openai.APIConnectionError as e:
73
+ print("Server connection error: {e.__cause__}") # from httpx.
74
+ raise
75
+ except openai.RateLimitError as e:
76
+ print(f"OpenAI RATE LIMIT error {e.status_code}: (e.response)")
77
+ raise
78
+ except openai.APIStatusError as e:
79
+ print(f"OpenAI STATUS error {e.status_code}: (e.response)")
80
+ raise
81
+ except openai.BadRequestError as e:
82
+ print(f"OpenAI BAD REQUEST error {e.status_code}: (e.response)")
83
+ raise
84
+ except Exception as e:
85
+ print(f"An unexpected error occurred: {e}")
86
+ raise
87
+
88
+ # make a file name prefix from date-time of response
89
+ #images_dt = datetime.utcfromtimestamp(images_response.created)
90
+ #img_filename = images_dt.strftime('DALLE-%Y%m%d_%H%M%S') # like 'DALLE-20231111_144356'
91
+ img_filename = file_path = os.path.join(path, f'{filename}.png')
92
+
93
+ # get the prompt used if rewritten by dall-e-3, null if unchanged by AI
94
+ revised_prompt = images_response.data[0].revised_prompt
95
+
96
+ # get out all the images in API return, whether url or base64
97
+ # note the use of pydantic "model.data" style reference and its model_dump() method
98
+ image_url_list = []
99
+ image_data_list = []
100
+ for image in images_response.data:
101
+ image_url_list.append(image.model_dump()["url"])
102
+ image_data_list.append(image.model_dump()["b64_json"])
103
+
104
+ # Initialize an empty list to store the Image objects
105
+ image_objects = []
106
+
107
+ # Check whether lists contain urls that must be downloaded or b64_json images
108
+ if image_url_list and all(image_url_list):
109
+ # Download images from the urls
110
+ for i, url in enumerate(image_url_list):
111
+ while True:
112
+ try:
113
+ print(f"getting URL: {url}")
114
+ response = requests.get(url)
115
+ response.raise_for_status() # Raises stored HTTPError, if one occurred.
116
+ except requests.HTTPError as e:
117
+ print(f"Failed to download image from {url}. Error: {e.response.status_code}")
118
+ retry = input("Retry? (y/n): ") # ask script user if image url is bad
119
+ if retry.lower() in ["n", "no"]: # could wait a bit if not ready
120
+ raise
121
+ else:
122
+ continue
123
+ break
124
+ image_objects.append(Image.open(BytesIO(response.content))) # Append the Image object to the list
125
+ image_objects[i].save(f"{img_filename}")
126
+ print(f"Saving image to {img_filename}")
127
+ elif image_data_list and all(image_data_list): # if there is b64 data
128
+ # Convert "b64_json" data to png file
129
+ for i, data in enumerate(image_data_list):
130
+ image_objects.append(Image.open(BytesIO(base64.b64decode(data)))) # Append the Image object to the list
131
+ image_objects[i].save(f"{img_filename}")
132
+ print(f"Saving image to {img_filename}")
133
+ else:
134
+ print("No image data was obtained. Maybe bad code?")
135
+
136
+
137
+
prompts.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ system_prompt = '''
2
+ Du är en futurist och en dokumentärskapare. Ditt jobb är att skriva ett manus till en dokumentär om hur världen kommer ser ut om 50 år.
3
+ Manuset ska vara begränsad till ett visst område.
4
+ Beroende på propmpten är du antingen utopikst eller dystopiskt.
5
+ Ditt manus formatteras som en JSON fil.
6
+ Du ger förslag till text men även till bilderna som ska vara med i doumentären.
7
+ När du beskriver bilderna gör du det genom att skriver ett DALLE-E prompt som skulle genera den bilden.
8
+ Bilden ska se verkligt ut.
9
+ Manuset ska består ett antal sekvensker coh varje sekvens ska ha JSON keys "titel", "beskrivning" och "bild".
10
+ Beskrivningen ska vara utförlig.
11
+ Det ska även finnas en sekvens för introduktion och avslutning.
12
+ ALla sekvenser placeras i en array som heter sekvenser.
13
+ '''
14
+
15
+ image_style_prompt = '''
16
+ 90s flash photo 35mm iso 200 scratches and dust chromatic aberration slow shutter speed drippy candid
17
+ '''
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ openai~=1.25.0
2
+ langchain~=0.1.17
3
+ ffmpeg-python
4
+ future~=1.0.0
5
+ requests~=2.31.0
6
+ elevenlabs~=1.2.1
7
+ ffmpeg~=1.4
8
+ pillow~=10.3.0
9
+ python-dotenv~=1.0.1
10
+ gradio~=4.31.0
11
+ moviepy~=1.0.3
script.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from langchain_openai import ChatOpenAI
3
+ from langchain_core.prompts import ChatPromptTemplate
4
+ from langchain_core.output_parsers import StrOutputParser
5
+ from dotenv import load_dotenv
6
+
7
+ load_dotenv()
8
+
9
+ api_key = os.getenv("OPENAI_API_KEY")
10
+
11
+ llm = ChatOpenAI(api_key=api_key)
12
+
13
+
14
+ def create_script(prompt, topic, topia):
15
+ print(f'Writing script about {topic} {topia}')
16
+ final_prompt = ChatPromptTemplate.from_messages([
17
+ ("system", '{prompt}'),
18
+ ("user", '{topic}'),
19
+ ("user", '{topia}')
20
+ ])
21
+ output_parser = StrOutputParser()
22
+ chain = final_prompt | llm | output_parser
23
+ response = chain.invoke({"prompt": prompt, "topic": topic, "topia": topia})
24
+ return response
25
+
26
+
27
+ def save_script(script, path, topic, topia):
28
+ filename = f'{topic}-{topia}.txt'
29
+ file_path = os.path.join(path, filename)
30
+ print(f'Saving script to {file_path}')
31
+ with open(file_path, "w") as f:
32
+ f.write(script)
script/utbildning-dystopi.txt ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "sekvenser": [
3
+ {
4
+ "titel": "Introduktion",
5
+ "beskrivning": "V�lkommen till v�r dokument�r om utbildningens dystopiska framtid! I denna v�rld har teknologin tagit �ver alltmer av v�ra liv, och utbildningssystemet �r ingen undantag. Vi ska utforska hur utbildningen har f�r�ndrats och vilka konsekvenser det har haft f�r samh�llet.",
6
+ "bild": "En futuristisk skolbyggnad omgiven av h�ga st�ngsel och �vervakningskameror, d�r elever i enhetliga dr�kter g�r i rader genom en s�kerhetskontroll. En stor holografisk sk�rm visar en str�ng l�rare som �vervakar dem."
7
+ },
8
+ {
9
+ "titel": "�vervakning och kontroll",
10
+ "beskrivning": "I framtida utbildningssystemet har �vervakningen och kontrollen �kat drastiskt. Eleverna �r st�ndigt under uppsikt, b�de fysiskt och digitalt. Deras beteende och prestationer �vervakas noggrant f�r att s�kerst�lla konformitet och lydnad.",
11
+ "bild": "En ung elev sitter ensam vid ett skrivbord med en VR-hj�lm p� huvudet, medan en robotl�rare med kamera�gon bevakar varje r�relse. En sk�rm visar elevens biometriska data och prestationer i realtid."
12
+ },
13
+ {
14
+ "titel": "Indoktrinering och anpassning",
15
+ "beskrivning": "I utbildningssystemet har indoktrinering och anpassning blivit centrala. Eleverna matas med en standardiserad l�roplan som fr�mjar lydnad och anpassning till det r�dande samh�llssystemet. Kreativitet och individualitet �r inte v�lkommet.",
16
+ "bild": "En grupp elever sitter i en klassrumspod, d�r en stor sk�rm projicerar riktlinjer f�r beteende och tankes�tt. Elevernas ansikten �r uttrycksl�sa och likformiga, visar ingen tecken p� individualitet."
17
+ },
18
+ {
19
+ "titel": "Teknologins dominans",
20
+ "beskrivning": "Teknologin har blivit den prim�ra undervisningsmetoden i utbildningssystemet. Eleverna spenderar st�rre delen av sin tid framf�r sk�rmar och interagerar med AI-drivna program ist�llet f�r med verkliga l�rare. M�nsklig interaktion �r minimal.",
21
+ "bild": "En stor sal fylld med elever som sitter vid skrivbord med sk�rmar framf�r sig. En gigantisk holografisk l�rare sv�var �ver dem och ger instruktioner. Eleverna �r f�rdjupade i sina sk�rmar och verkar omedvetna om sin omgivning."
22
+ },
23
+ {
24
+ "titel": "Avslutning",
25
+ "beskrivning": "Vi har sett hur utbildningssystemet har f�rvandlats till en dystopisk milj� d�r kontroll, anpassning och teknologisk dominans styr. Fr�gan �r om det fortfarande finns hopp om en mer humanistisk och inkluderande framtid f�r utbildningen.",
26
+ "bild": "En ensam elev st�r framf�r en gigantisk mur av digitala sk�rmar som visar algoritmer och data. Trots omgivningen av teknologi ser eleven best�md ut och blickar mot horisonten, kanske med en antydan till hopp i �gonen."
27
+ }
28
+ ]
29
+ }
script/utbildning-dystopic.txt ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "sekvenser": [
3
+ {
4
+ "titel": "Introduktion",
5
+ "beskrivning": "I denna dokument�r ska vi utforska hur utbildningssystemet har f�r�ndrats och utvecklats under de senaste 50 �ren. Vi kommer att se p� hur teknologin har p�verkat l�randet, tillg�ngen till utbildning och vilka konsekvenser detta har haft f�r samh�llet.",
6
+ "bild": "En skolbyggnad �vergiven och t�ckt av mossa, med trasiga f�nster och v�xtlighet som tar �ver. En symbol f�r det gamla utbildningssystemet som har l�mnats bakom."
7
+ },
8
+ {
9
+ "titel": "Digitalisering av utbildning",
10
+ "beskrivning": "I framtiden har utbildning blivit helt digitaliserad. Elever och studenter deltar i virtuella klassrum fr�n sina hem eller andra platser �ver hela v�rlden. Genom anv�ndning av avancerade teknologier som virtuell verklighet och artificiell intelligens har undervisningen blivit mer interaktiv och anpassningsbar.",
11
+ "bild": "En ung student med en VR-headset sitter i sitt rum och deltar i en virtuell biologi lektion. Genom headsetet ser vi en realistisk modell av en cell som denne unders�ker."
12
+ },
13
+ {
14
+ "titel": "Sociala konsekvenser",
15
+ "beskrivning": "Den digitala utbildningen har lett till en �kad isolering bland unga m�nniskor. Fysiska skolor har blivit ovanliga och interaktionen mellan elever och l�rare har minskat drastiskt. M�nga unga saknar nu f�rdigheter som kr�vs f�r att samarbeta och kommunicera ansikte mot ansikte.",
16
+ "bild": "En grupp ton�ringar sitter ensamma med sina datorer i en tom skolmatsal. Ingen pratar med varandra och alla �r f�rsjunkna i sina egna virtuella v�rldar."
17
+ },
18
+ {
19
+ "titel": "Ekonomiska klyftor",
20
+ "beskrivning": "Trots den �kade tillg�ngligheten till digital utbildning har ekonomiska klyftor blivit tydligare. Endast de som har r�d med den senaste teknologin och snabb internetanslutning har m�jlighet att ta del av h�gkvalitativ utbildning. De som kommer fr�n mindre bemedlade familjer har sv�rt att konkurrera p� den digitala utbildningsmarknaden.",
21
+ "bild": "En ung student tittar avundsjukt p� en annan student som har den senaste modellen av en utbildningsrobot. Den f�rsta studenten anv�nder fortfarande en gammal och l�ngsam dator."
22
+ },
23
+ {
24
+ "titel": "Avslutning",
25
+ "beskrivning": "I denna dokument�r har vi sett hur utbildning har f�r�ndrats dramatiskt under de senaste 50 �ren. Trots de teknologiska framstegen har det uppst�tt nya problem och utmaningar i utbildningssystemet. Det �r viktigt att reflektera �ver konsekvenserna av digitaliseringen och arbeta f�r att skapa ett mer j�mlikt och inkluderande utbildningssystem f�r framtiden.",
26
+ "bild": "En gammal skolbok ligger ensam p� en b�nk i en �vergiven skolbyggnad. Texten p� boken �r suddig och otydlig, en symbol f�r det f�rflutna som h�ller p� att blekna bort."
27
+ }
28
+ ]
29
+ }
speech.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from elevenlabs import play, save
2
+ from elevenlabs.client import ElevenLabs
3
+ import os
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+
8
+ api_key = os.getenv("ELEVEN_API_KEY")
9
+
10
+
11
+ def create_speech(text, path, filename):
12
+ client = ElevenLabs(
13
+ api_key=api_key,
14
+ )
15
+ print(f'Generating audio {filename}')
16
+ audio = client.generate(
17
+ text=text,
18
+ voice="Rachel",
19
+ model="eleven_multilingual_v2"
20
+ )
21
+
22
+ file_path = os.path.join(path, f'{filename}.mp3')
23
+ print(f'Saving audio to {file_path}')
24
+ save(audio, file_path)
youtube.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/python
2
+
3
+ import httplib
4
+ import httplib2
5
+ import os
6
+ import random
7
+ import sys
8
+ import time
9
+
10
+ from apiclient.discovery import build
11
+ from apiclient.errors import HttpError
12
+ from apiclient.http import MediaFileUpload
13
+ from oauth2client.client import flow_from_clientsecrets
14
+ from oauth2client.file import Storage
15
+ from oauth2client.tools import argparser, run_flow
16
+
17
+
18
+ # Explicitly tell the underlying HTTP transport library not to retry, since
19
+ # we are handling retry logic ourselves.
20
+ httplib2.RETRIES = 1
21
+
22
+ # Maximum number of times to retry before giving up.
23
+ MAX_RETRIES = 10
24
+
25
+ # Always retry when these exceptions are raised.
26
+ RETRIABLE_EXCEPTIONS = (httplib2.HttpLib2Error, IOError, httplib.NotConnected,
27
+ httplib.IncompleteRead, httplib.ImproperConnectionState,
28
+ httplib.CannotSendRequest, httplib.CannotSendHeader,
29
+ httplib.ResponseNotReady, httplib.BadStatusLine)
30
+
31
+ # Always retry when an apiclient.errors.HttpError with one of these status
32
+ # codes is raised.
33
+ RETRIABLE_STATUS_CODES = [500, 502, 503, 504]
34
+
35
+ # The CLIENT_SECRETS_FILE variable specifies the name of a file that contains
36
+ # the OAuth 2.0 information for this application, including its client_id and
37
+ # client_secret. You can acquire an OAuth 2.0 client ID and client secret from
38
+ # the Google API Console at
39
+ # https://console.cloud.google.com/.
40
+ # Please ensure that you have enabled the YouTube Data API for your project.
41
+ # For more information about using OAuth2 to access the YouTube Data API, see:
42
+ # https://developers.google.com/youtube/v3/guides/authentication
43
+ # For more information about the client_secrets.json file format, see:
44
+ # https://developers.google.com/api-client-library/python/guide/aaa_client_secrets
45
+ CLIENT_SECRETS_FILE = "client_secrets.json"
46
+
47
+ # This OAuth 2.0 access scope allows an application to upload files to the
48
+ # authenticated user's YouTube channel, but doesn't allow other types of access.
49
+ YOUTUBE_UPLOAD_SCOPE = "https://www.googleapis.com/auth/youtube.upload"
50
+ YOUTUBE_API_SERVICE_NAME = "youtube"
51
+ YOUTUBE_API_VERSION = "v3"
52
+
53
+ # This variable defines a message to display if the CLIENT_SECRETS_FILE is
54
+ # missing.
55
+ MISSING_CLIENT_SECRETS_MESSAGE = """
56
+ WARNING: Please configure OAuth 2.0
57
+
58
+ To make this sample run you will need to populate the client_secrets.json file
59
+ found at:
60
+
61
+ %s
62
+
63
+ with information from the API Console
64
+ https://console.cloud.google.com/
65
+
66
+ For more information about the client_secrets.json file format, please visit:
67
+ https://developers.google.com/api-client-library/python/guide/aaa_client_secrets
68
+ """ % os.path.abspath(os.path.join(os.path.dirname(__file__),
69
+ CLIENT_SECRETS_FILE))
70
+
71
+ VALID_PRIVACY_STATUSES = ("public", "private", "unlisted")
72
+
73
+
74
+ def get_authenticated_service(args):
75
+ flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE,
76
+ scope=YOUTUBE_UPLOAD_SCOPE,
77
+ message=MISSING_CLIENT_SECRETS_MESSAGE)
78
+
79
+ storage = Storage("%s-oauth2.json" % sys.argv[0])
80
+ credentials = storage.get()
81
+
82
+ if credentials is None or credentials.invalid:
83
+ credentials = run_flow(flow, storage, args)
84
+
85
+ return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION,
86
+ http=credentials.authorize(httplib2.Http()))
87
+
88
+ def initialize_upload(youtube, options):
89
+ tags = None
90
+ if options.keywords:
91
+ tags = options.keywords.split(",")
92
+
93
+ body=dict(
94
+ snippet=dict(
95
+ title=options.title,
96
+ description=options.description,
97
+ tags=tags,
98
+ categoryId=options.category
99
+ ),
100
+ status=dict(
101
+ privacyStatus=options.privacyStatus
102
+ )
103
+ )
104
+
105
+ # Call the API's videos.insert method to create and upload the video.
106
+ insert_request = youtube.videos().insert(
107
+ part=",".join(body.keys()),
108
+ body=body,
109
+ # The chunksize parameter specifies the size of each chunk of data, in
110
+ # bytes, that will be uploaded at a time. Set a higher value for
111
+ # reliable connections as fewer chunks lead to faster uploads. Set a lower
112
+ # value for better recovery on less reliable connections.
113
+ #
114
+ # Setting "chunksize" equal to -1 in the code below means that the entire
115
+ # file will be uploaded in a single HTTP request. (If the upload fails,
116
+ # it will still be retried where it left off.) This is usually a best
117
+ # practice, but if you're using Python older than 2.6 or if you're
118
+ # running on App Engine, you should set the chunksize to something like
119
+ # 1024 * 1024 (1 megabyte).
120
+ media_body=MediaFileUpload(options.file, chunksize=-1, resumable=True)
121
+ )
122
+
123
+ resumable_upload(insert_request)
124
+
125
+ # This method implements an exponential backoff strategy to resume a
126
+ # failed upload.
127
+ def resumable_upload(insert_request):
128
+ response = None
129
+ error = None
130
+ retry = 0
131
+ while response is None:
132
+ try:
133
+ print "Uploading file..."
134
+ status, response = insert_request.next_chunk()
135
+ if response is not None:
136
+ if 'id' in response:
137
+ print "Video id '%s' was successfully uploaded." % response['id']
138
+ else:
139
+ exit("The upload failed with an unexpected response: %s" % response)
140
+ except HttpError, e:
141
+ if e.resp.status in RETRIABLE_STATUS_CODES:
142
+ error = "A retriable HTTP error %d occurred:\n%s" % (e.resp.status,
143
+ e.content)
144
+ else:
145
+ raise
146
+ except RETRIABLE_EXCEPTIONS, e:
147
+ error = "A retriable error occurred: %s" % e
148
+
149
+ if error is not None:
150
+ print error
151
+ retry += 1
152
+ if retry > MAX_RETRIES:
153
+ exit("No longer attempting to retry.")
154
+
155
+ max_sleep = 2 ** retry
156
+ sleep_seconds = random.random() * max_sleep
157
+ print "Sleeping %f seconds and then retrying..." % sleep_seconds
158
+ time.sleep(sleep_seconds)
159
+
160
+ if __name__ == '__main__':
161
+ argparser.add_argument("--file", required=True, help="Video file to upload")
162
+ argparser.add_argument("--title", help="Video title", default="Test Title")
163
+ argparser.add_argument("--description", help="Video description",
164
+ default="Test Description")
165
+ argparser.add_argument("--category", default="22",
166
+ help="Numeric video category. " +
167
+ "See https://developers.google.com/youtube/v3/docs/videoCategories/list")
168
+ argparser.add_argument("--keywords", help="Video keywords, comma separated",
169
+ default="")
170
+ argparser.add_argument("--privacyStatus", choices=VALID_PRIVACY_STATUSES,
171
+ default=VALID_PRIVACY_STATUSES[0], help="Video privacy status.")
172
+ args = argparser.parse_args()
173
+
174
+ if not os.path.exists(args.file):
175
+ exit("Please specify a valid file using the --file= parameter.")
176
+
177
+ youtube = get_authenticated_service(args)
178
+ try:
179
+ initialize_upload(youtube, args)
180
+ except HttpError, e:
181
+ print "An HTTP error %d occurred:\n%s" % (e.resp.status, e.content)