This commit is contained in:
tsuky_chen
2024-04-05 01:58:00 +08:00
2070 changed files with 605 additions and 331084 deletions

19
.vscode/launch.json vendored
View File

@@ -1,19 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Current File with Arguments",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"args": [
"--path_to_vm", "/Users/lxc/Virtual Machines.localized/DesktopEnv-Ubuntu 64-bit Arm.vmwarevm/DesktopEnv-Ubuntu 64-bit Arm.vmx"
// "--example_time_limit", "60"
]
}
]
}

View File

@@ -178,7 +178,7 @@
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright contributors
Copyright 2024 XLANG NLP Lab
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

151
README.md
View File

@@ -1,92 +1,123 @@
# OSWorld: Open-Ended Tasks in Real Computer Environments
# OSWorld: Benchmarking Multimodal Agents for Open-Ended Tasks in Real Computer Environments
<p align="center">
<img src="desktop_env/assets/icon.jpg" alt="Logo" width="80px">
<br>
<b>SLOGAN</b>
</p>
<p align="center">
<a href="">Website</a>
<a href="https://os-world.github.io/">Website</a>
<a href="">Paper</a>
</p>
![Overview]()
## Updates
- 2024-03-01: We released our [paper](), [environment code](), [dataset](), and [project page](). Check it out!
- 2024-04-04: We released our [paper](), [environment and benchmark](https://github.com/xlang-ai/OSWorld), and [project page](https://os-world.github.io/). Check it out!
## Install
1. Install VMWare and configure `vmrun` command:
Please refer to [guidance](https://docs.google.com/document/d/1KBdeZwmZs2Vi_Wsnngb3Wf1-RiwMMpXTftwMqP2Ztak/edit#heading=h.uh0x0tkl7fuw)
## Installation
### On Your Desktop or Server (Non-Virtualized Platform)
Suppose you are operating on a system that has not been virtualized, meaning you are not utilizing a virtualized environment like AWS, Azure, or k8s. If this is the case, proceed with the instructions below. However, if you are on a virtualized platform, please refer to the [virtualized platform](https://github.com/xlang-ai/OSWorld?tab=readme-ov-file#virtualized-platform) section.
2. Install the environment package, download the examples and the virtual machine image.
For x86_64 Linux or Windows, you can install the environment package and download the examples and the virtual machine image by running the following commands:
1. First, clone this repository and `cd` into it. Then, install the dependencies listed in `requirements.txt`. It is recommended that you use the latest version of Conda to manage the environment, but you can also choose to manually install the dependencies. Please ensure that the version of Python is >= 3.9.
```bash
git clone https://github.com/xlang-ai/DesktopEnv
cd DesktopEnv
# Clone the OSWorld repository
git clone https://github.com/xlang-ai/OSWorld
# Change directory into the cloned repository
cd OSWorld
# Optional: Create a Conda environment for OSWorld
# conda create -n osworld python=3.9
# conda activate osworld
# Install required dependencies
pip install -r requirements.txt
gdown https://drive.google.com/drive/folders/1HX5gcf7UeyR-2UmiA15Q9U-
Wr6E6Gio8 -O Ubuntu --folder
```
2. Install [VMware Workstation Pro](https://www.vmware.com/products/workstation-pro/workstation-pro-evaluation.html) (for systems with Apple Chips, you should install [VMware Fusion](https://www.vmware.com/go/getfusion)) and configure the `vmrun` command. Verify the successful installation by running the following:
```bash
vmrun -T ws list
```
If the installation along with the environment variable set is successful, you will see the message showing the current running virtual machines.
3. Obtain the virtual machine image. If you are using Linux or Windows with an x86_64 CPU, install the environment package and download the examples and the virtual machine image by executing the following commands:
Remove the `nogui` parameter if you wish to view the activities within the virtual machine.
```bash
gdown https://drive.google.com/drive/folders/1HX5gcf7UeyR-2UmiA15Q9U-Wr6E6Gio8 -O Ubuntu --folder
vmrun -T ws start "Ubuntu/Ubuntu.vmx" nogui
vmrun -T ws snapshot "Ubuntu/Ubuntu.vmx" "init_state"
```
For macOS with Apple chips, you should install the specially prepared virtual machine image by executing the following commands:
```bash
gdown https://drive.google.com/drive/folders/1wT0vwpuEFTIPik9Tjn4DWoZ2oHCD7tM0 -O Ubuntu --folder
vmrun -T fusion start "Ubuntu/DesktopEnv-Ubuntu 64-bit Arm.vmx"
vmrun -T fusion snapshot "Ubuntu/DesktopEnv-Ubuntu 64-bit Arm.vmx" "init_state"
```
### On AWS or Azure (Virtualized platform)
We are working on supporting it 👷. Please hold tight!
## Quick Start
Run the following minimal example to interact with the environment:
```python
import json
from desktop_env.envs.desktop_env import DesktopEnv
with open("evaluation_examples/examples/gimp/f723c744-e62c-4ae6-98d1-750d3cd7d79d.json", "r", encoding="utf-8") as f:
example = json.load(f)
example = {
"id": "94d95f96-9699-4208-98ba-3c3119edf9c2",
"instruction": "I want to install Spotify on my current system. Could you please help me?",
"config": [
{
"type": "execute",
"parameters": {
"command": [
"python",
"-c",
"import pyautogui; import time; pyautogui.click(960, 540); time.sleep(0.5);"
]
}
}
],
"evaluator": {
"func": "check_include_exclude",
"result": {
"type": "vm_command_line",
"command": "which spotify"
},
"expected": {
"type": "rule",
"rules": {
"include": ["spotify"],
"exclude": ["not found"]
}
}
}
}
env = DesktopEnv(
path_to_vm=r"path_to_vm",
action_space="computer_13",
task_config=example
path_to_vm=r"Ubuntu/DesktopEnv-Ubuntu 64-bit Arm.vmx",
action_space="pyautogui"
)
observation = env.reset()
observation, reward, done, info = env.step({"action_type": "CLICK", "parameters": {"button": "right", "num_clicks": 1}})
obs = env.reset(task_config=example)
obs, reward, done, info = env.step("pyautogui.rightClick()")
```
You will see all the logs of the system running normally, including the successful creation of the environment, completion of setup, and successful execution of actions. In the end, you will observe a successful right-click on the screen, which means you are ready to go.
## Experiments
### Agent Baselines
If you wish to run the baseline agent used in our paper, you can execute the following command as an example under the GPT-4V pure-screenshot setting:
```bash
python run.py --path_to_vm Ubuntu/Ubuntu.vmx --headless --observation_type screenshot --model gpt-4-vision-preview --result_dir ./results
```
The results, which include screenshots, actions, and video recordings of the agent's task completion, will be saved in the `./results` directory in this case. You can then run the following command to obtain the result:
```bash
python show_result.py
```
## Annotation Tool Usage
We provide an annotation tool to help you annotate the examples.
## Agent Usage
We provide a simple agent to interact with the environment. You can use it as a starting point to build your own agent.
## Road map of infra (Proposed)
- [x] Explore VMWare, and whether it can be connected and control through mouse package
- [x] Explore Windows and MacOS, whether it can be installed
- MacOS is closed source and cannot be legally installed
- Windows is available legally and can be installed
- [x] Build gym-like python interface for controlling the VM
- [x] Recording of actions (mouse movement, click, keyboard) for humans to annotate, and we can replay it and compress it
- [x] Build a simple task, e.g. open a browser, open a website, click on a button, and close the browser
- [x] Set up a pipeline and build agents implementation (zero-shot) for the task
- [x] Start to design on which tasks inside the DesktopENv to focus on, start to wrap up the environment to be public
- [x] Start to annotate the examples for ~~training~~ and testing
- [x] Error handling during file passing and file opening, etc.
- [x] Add accessibility tree from the OS into the observation space
- [x] Add pre-process and post-process action support for benchmarking setup and evaluation
- [ ] Multiprocess support, this can enable the reinforcement learning to be more efficient
- [ ] Experiment logging and visualization system
- [ ] Add more tasks, maybe scale to 300 for v1.0.0, and create a dynamic leaderboard
## Road map of benchmark, tools and resources (Proposed)
- [ ] Improve the annotation tool base on DuckTrack, make it more robust which align on accessibility tree
- [ ] Annotate the steps of doing the task
- [ ] Build a website for the project
- [ ] Crawl all resources we explored from the internet, and make it easy to access
- [ ] Set up ways for community to contribute new examples
### Evaluation
Please start by reading through the [agent interface](https://github.com/xlang-ai/OSWorld/blob/main/mm_agents/README.md) and the [environment interface](https://github.com/xlang-ai/OSWorld/blob/main/desktop_env/README.md).
Correctly implement the agent interface and import your customized version in the `run.py` file.
Afterward, you can execute a command similar to the one in the previous section to run the benchmark on your agent.
## Citation
If you find this environment useful, please consider citing our work:
```
@article{DesktopEnv,
@article{OSWorld,
title={},
author={},
journal={arXiv preprint arXiv:xxxx.xxxx},

30
ROADMAP.md Normal file
View File

@@ -0,0 +1,30 @@
# Road Map
Here we provide a high-level road map for the project. We will update this road map as we make progress.
If you are interested in contributing to the project, please check the [CONTRIBUTING.md](CONTRIBUTING.md) for more details.
## Road Map for Environment Infrastructure
- [x] Explore VMWare, and whether it can be connected and control through mouse package
- [x] Explore Windows and MacOS, whether it can be installed
- MacOS is closed source and cannot be legally installed
- Windows is available legally and can be installed
- [x] Build gym-like python interface for controlling the VM
- [x] Recording of actions (mouse movement, click, keyboard) for humans to annotate, and we can replay it and compress it
- [x] Build a simple task, e.g. open a browser, open a website, click on a button, and close the browser
- [x] Set up a pipeline and build agents implementation (zero-shot) for the task
- [x] Start to design on which tasks inside the DesktopENv to focus on, start to wrap up the environment to be public
- [x] Start to annotate the examples for ~~training~~ and testing
- [x] Error handling during file passing and file opening, etc.
- [x] Add accessibility tree from the OS into the observation space
- [x] Add pre-process and post-process action support for benchmarking setup and evaluation
- [x] Experiment logging and visualization system
- [x] Add more tasks, maybe scale to 300 for v1.0.0, and create a dynamic leaderboard
- [x] Multiprocess support, this can enable the reinforcement learning to be more efficient
- [ ] Support running on platform that have nested virtualization, e.g. Google Cloud, AWS, etc.
## Road Map of Annotation Tool
- [ ] Improve the annotation tool base on DuckTrack, make it more robust which align on accessibility tree
- [ ] Annotate the steps of doing the task
- [ ] Crawl all resources we explored from the internet, and make it easy to access
- [ ] Set up ways for community to contribute new examples

172
annotation/.gitignore vendored
View File

@@ -1,172 +0,0 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# experiments
experiments/**/*.png
experiments/**/*.csv
experiments/**/*.mp4
experiments/**/*.jsonl
experiments/**/*.json
experiments/**/*.md
experiments/**/*.txt
# macos
*DS_Store*

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 DuckAI
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,50 +0,0 @@
# OBS Setup
These are instructions on setting up OBS (Open Broadcaster Software) to record screen activity for creating the multimodal computer dataset.
## Installation
1. Go to the OBS Project website: [https://obsproject.com/](https://obsproject.com/).
2. Choose the appropriate installer for your operating system.
3.
![Operating System Selection](readme_images/Screenshot%202023-06-17%20220155.png)
3. Run the installer from your downloads folder and grant OBS the necessary permissions for installation.
![Installer Run](readme_images/Screenshot%202023-06-24%20115916.png)
4. Keep the default settings and proceed through the installation wizard by clicking "Next" and then "Finish."
![Installation Wizard](readme_images/Screenshot%202023-06-24%20120133.png)
5. OBS should now be open. If not, search for and open the application.
![Open OBS](readme_images/Screenshot%202023-06-17%20221407.png)
## Enabling OBS WebSocket Server
1. Click on "Tools" in the Navigation Bar within OBS, and then select "WebSocket Server Settings." A pop-up window will appear.
![WebSocket Server Settings](readme_images/Screenshot%202023-06-17%20221553.png)
2. Check the box next to "Enable WebSocket server" and uncheck the box next to "Enable Authentication." Click "Apply," then "Ok." You should return to the main OBS page.
Make sure the port is set to 4455.
![Enable WebSocket Server](readme_images/Screenshot%202023-06-24%20120347.png)
## Adding Display Capture and Recording
1. Now, back on the home page of OBS, select "Scene." Under "Sources," click the "+" button and then click "Display Capture." (in MacOS this is MacOS Screen Capture)
![Display Capture](readme_images/Screenshot%202023-06-24%20110823.png)
2. Select "Ok."
![Confirm Display Capture](readme_images/Screenshot%202023-06-24%20111017.png)
3. Make sure the "Display" is set to your main display, and you should see your screen on the canvas. Select "Ok." _(in MacOS if your screen is black with a red square in the top left try to disable then re-enable OBS Screen Recording permissions, this has worked before)_
![Main Display](readme_images/Screenshot%202023-06-24%20112001.png)
4. Now you can close OBS and OBS will opened and controlled automatically when you launch the Computer Tracker App. Also, the Computer Tracker app creates a new OBS profile so you don't have to worry about your previous settings being messed up.
![Recording in Progress](readme_images/Screenshot%202023-06-24%20113548.png)

View File

@@ -1,98 +0,0 @@
# DuckTrack
This is the repository for the DuckAI DuckTrack app which records all keyboard and mouse input as well as the screen for use in a multimodal computer interaction dataset.
## Installation & Setup
### Download Application
<!-- TODO: add prebuilt applications in github releases -->
Download the pre-built application for your system [here](https://github.com/TheDuckAI/DuckTrack/releases/).
Make sure you have OBS downloaded with the following configuration:
1. Have a screen capture source recording your whole main screen.
2. Enable desktop audio and mute microphone.
3. Make sure the default websocket is enabled.
More detailed instructions for OBS setup and installation located [here](OBS_SETUP.md).
If you are on MacOS, make sure to enable to the following Privacy & Security permissions before running the app:
1. Accessibility (for playing back actions)
2. Input Monitoring (for reading keyboard inputs)
Make sure to accept all other security permission dialogues to ensure that the app works properly.
### Build from source
Have Python >=3.11.
Clone this repo and `cd` into it:
```bash
$ git clone https://github.com/TheDuckAI/DuckTrack
$ cd DuckTrack
```
Install the dependencies for this project:
```bash
$ pip install -r requirements.txt
```
Build the application:
```bash
$ python3 build.py
```
The built application should be located in the generated `dist` directory. After this, follow the remaining relevant setup instructions.
## Running the App
You can run the app like any other desktop app on your computer. If you decided to not download the app or build it from source, just run `python main.py` and it should work the same. You will be interacting with the app through an app tray icon or a small window.
### Recording
From the app tray or GUI, you can start and stop a recording as well as pause and resume a recording. Pausing and resuming is important for when you want to hide sensitive information like credit card of login credentials. You can optionally name your recording and give it a description upon stopping a recording. You can also view your recordings by pressing the "Show Recordings" option.
### Playback
You can playback a recording, i.e. simulate the series of events from the recording, by pressing "Play Latest Recording", which plays the latest created recording, or by pressing "Play Custom Recording", which lets you choose a recording to play. You can easily replay the most recently played recording by pressing "Replay Recording".
To stop the app mid-playback, just press `shift`+`esc` on your keyboard.
### Misc
To quit the app, you just press the "Quit" option.
## Recording Format
Recordings are stored in `Documents/DuckTrack_Recordings`. Each recording is a directory containing:
1. `events.jsonl` file - sequence of all computer actions that happened. A sample event may look like this:
```json
{"time_stamp": 1234567.89, "action": "move", "x": 69.0, "y": 420.0}
```
1. `metadata.json` - stores metadata about the computer that made the recording
2. `README.md` - stores the description for the recording
3. MP4 file - the screen recording from OBS of the recording.
Here is a [sample recording](example) for further reference.
## Technical Overview
<!-- maybe put a nice graphical representation of the app here -->
*TDB*
## Known Bugs
- After doing lots of playbacks on macOS, a segfault will occur.
- Mouse movement is not captured when the current application is using raw input, i.e. video games.
- OBS may not open in the background properly on some Linux machines.
## Things To Do
- Add logging
- Testing
- CI (with builds and testing)
- Add way to hide/show window from the app tray (and it saves that as a preference?)
- Make saving preferences a thing generally, like with natural scrolling too

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -1,27 +0,0 @@
import shutil
import sys
from pathlib import Path
from platform import system
from subprocess import CalledProcessError, run
project_dir = Path(".")
assets_dir = project_dir / "assets"
main_py = project_dir / "main.py"
icon_file = assets_dir / ("duck.ico" if system() == "Windows" else "duck.png")
for dir_to_remove in ["dist", "build"]:
dir_path = project_dir / dir_to_remove
if dir_path.exists():
shutil.rmtree(dir_path)
pyinstaller_cmd = [
"pyinstaller", "--onefile", "--windowed",
f"--add-data={assets_dir}{';' if system() == 'Windows' else ':'}{assets_dir}",
f"--name=DuckTrack", f"--icon={icon_file}", str(main_py)
]
try:
run(pyinstaller_cmd, check=True)
except CalledProcessError as e:
print("An error occurred while running PyInstaller:", e)
sys.exit(1)

View File

@@ -1 +0,0 @@
from .app import MainInterface

View File

@@ -1,251 +0,0 @@
import os
import sys
from platform import system
from PyQt6.QtCore import QTimer, pyqtSlot
from PyQt6.QtGui import QAction, QIcon
from PyQt6.QtWidgets import (QApplication, QCheckBox, QDialog, QFileDialog,
QFormLayout, QLabel, QLineEdit, QMenu,
QMessageBox, QPushButton, QSystemTrayIcon,
QTextEdit, QVBoxLayout, QWidget)
from .obs_client import close_obs, is_obs_running, open_obs
from .playback import Player, get_latest_recording
from .recorder import Recorder
from .util import get_recordings_dir, open_file
class TitleDescriptionDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Recording Details")
layout = QVBoxLayout(self)
self.form_layout = QFormLayout()
self.title_label = QLabel("Title:")
self.title_input = QLineEdit(self)
self.form_layout.addRow(self.title_label, self.title_input)
self.description_label = QLabel("Description:")
self.description_input = QTextEdit(self)
self.form_layout.addRow(self.description_label, self.description_input)
layout.addLayout(self.form_layout)
self.submit_button = QPushButton("Save", self)
self.submit_button.clicked.connect(self.accept)
layout.addWidget(self.submit_button)
def get_values(self):
return self.title_input.text(), self.description_input.toPlainText()
class MainInterface(QWidget):
def __init__(self, app: QApplication):
super().__init__()
self.tray = QSystemTrayIcon(QIcon(resource_path("assets/duck.png")))
self.tray.show()
self.app = app
self.init_tray()
self.init_window()
if not is_obs_running():
self.obs_process = open_obs()
def init_window(self):
self.setWindowTitle("DuckTrack")
layout = QVBoxLayout(self)
self.toggle_record_button = QPushButton("Start Recording", self)
self.toggle_record_button.clicked.connect(self.toggle_record)
layout.addWidget(self.toggle_record_button)
self.toggle_pause_button = QPushButton("Pause Recording", self)
self.toggle_pause_button.clicked.connect(self.toggle_pause)
self.toggle_pause_button.setEnabled(False)
layout.addWidget(self.toggle_pause_button)
self.show_recordings_button = QPushButton("Show Recordings", self)
self.show_recordings_button.clicked.connect(lambda: open_file(get_recordings_dir()))
layout.addWidget(self.show_recordings_button)
self.play_latest_button = QPushButton("Play Latest Recording", self)
self.play_latest_button.clicked.connect(self.play_latest_recording)
layout.addWidget(self.play_latest_button)
self.play_custom_button = QPushButton("Play Custom Recording", self)
self.play_custom_button.clicked.connect(self.play_custom_recording)
layout.addWidget(self.play_custom_button)
self.replay_recording_button = QPushButton("Replay Recording", self)
self.replay_recording_button.clicked.connect(self.replay_recording)
self.replay_recording_button.setEnabled(False)
layout.addWidget(self.replay_recording_button)
self.quit_button = QPushButton("Quit", self)
self.quit_button.clicked.connect(self.quit)
layout.addWidget(self.quit_button)
self.natural_scrolling_checkbox = QCheckBox("Natural Scrolling", self, checked=system() == "Darwin")
layout.addWidget(self.natural_scrolling_checkbox)
self.natural_scrolling_checkbox.stateChanged.connect(self.toggle_natural_scrolling)
self.setLayout(layout)
def init_tray(self):
self.menu = QMenu()
self.tray.setContextMenu(self.menu)
self.toggle_record_action = QAction("Start Recording")
self.toggle_record_action.triggered.connect(self.toggle_record)
self.menu.addAction(self.toggle_record_action)
self.toggle_pause_action = QAction("Pause Recording")
self.toggle_pause_action.triggered.connect(self.toggle_pause)
self.toggle_pause_action.setVisible(False)
self.menu.addAction(self.toggle_pause_action)
self.show_recordings_action = QAction("Show Recordings")
self.show_recordings_action.triggered.connect(lambda: open_file(get_recordings_dir()))
self.menu.addAction(self.show_recordings_action)
self.play_latest_action = QAction("Play Latest Recording")
self.play_latest_action.triggered.connect(self.play_latest_recording)
self.menu.addAction(self.play_latest_action)
self.play_custom_action = QAction("Play Custom Recording")
self.play_custom_action.triggered.connect(self.play_custom_recording)
self.menu.addAction(self.play_custom_action)
self.replay_recording_action = QAction("Replay Recording")
self.replay_recording_action.triggered.connect(self.replay_recording)
self.menu.addAction(self.replay_recording_action)
self.replay_recording_action.setVisible(False)
self.quit_action = QAction("Quit")
self.quit_action.triggered.connect(self.quit)
self.menu.addAction(self.quit_action)
self.menu.addSeparator()
self.natural_scrolling_option = QAction("Natural Scrolling", checkable=True, checked=system() == "Darwin")
self.natural_scrolling_option.triggered.connect(self.toggle_natural_scrolling)
self.menu.addAction(self.natural_scrolling_option)
@pyqtSlot()
def replay_recording(self):
player = Player()
if hasattr(self, "last_played_recording_path"):
player.play(self.last_played_recording_path)
else:
self.display_error_message("No recording has been played yet!")
@pyqtSlot()
def play_latest_recording(self):
player = Player()
recording_path = get_latest_recording()
self.last_played_recording_path = recording_path
self.replay_recording_action.setVisible(True)
self.replay_recording_button.setEnabled(True)
player.play(recording_path)
@pyqtSlot()
def play_custom_recording(self):
player = Player()
directory = QFileDialog.getExistingDirectory(None, "Select Recording", get_recordings_dir())
if directory:
self.last_played_recording_path = directory
self.replay_recording_button.setEnabled(True)
self.replay_recording_action.setVisible(True)
player.play(directory)
@pyqtSlot()
def quit(self):
if hasattr(self, "recorder_thread"):
self.toggle_record()
if hasattr(self, "obs_process"):
close_obs(self.obs_process)
self.app.quit()
def closeEvent(self, event):
self.quit()
@pyqtSlot()
def toggle_natural_scrolling(self):
sender = self.sender()
if sender == self.natural_scrolling_checkbox:
state = self.natural_scrolling_checkbox.isChecked()
self.natural_scrolling_option.setChecked(state)
else:
state = self.natural_scrolling_option.isChecked()
self.natural_scrolling_checkbox.setChecked(state)
@pyqtSlot()
def toggle_pause(self):
if self.recorder_thread._is_paused:
self.recorder_thread.resume_recording()
self.toggle_pause_action.setText("Pause Recording")
self.toggle_pause_button.setText("Pause Recording")
else:
self.recorder_thread.pause_recording()
self.toggle_pause_action.setText("Resume Recording")
self.toggle_pause_button.setText("Resume Recording")
@pyqtSlot()
def toggle_record(self):
if not hasattr(self, "recorder_thread"):
self.recorder_thread = Recorder(natural_scrolling=self.natural_scrolling_checkbox.isChecked())
self.recorder_thread.recording_stopped.connect(self.on_recording_stopped)
self.recorder_thread.start()
self.update_menu(True)
else:
self.recorder_thread.stop_recording()
self.recorder_thread.terminate()
recording_dir = self.recorder_thread.recording_path
del self.recorder_thread
dialog = TitleDescriptionDialog()
QTimer.singleShot(0, dialog.raise_)
result = dialog.exec()
if result == QDialog.DialogCode.Accepted:
title, description = dialog.get_values()
if title:
renamed_dir = os.path.join(os.path.dirname(recording_dir), title)
os.rename(recording_dir, renamed_dir)
with open(os.path.join(renamed_dir, 'README.md'), 'w') as f:
f.write(description)
self.on_recording_stopped()
@pyqtSlot()
def on_recording_stopped(self):
self.update_menu(False)
def update_menu(self, is_recording: bool):
self.toggle_record_button.setText("Stop Recording" if is_recording else "Start Recording")
self.toggle_record_action.setText("Stop Recording" if is_recording else "Start Recording")
self.toggle_pause_button.setEnabled(is_recording)
self.toggle_pause_action.setVisible(is_recording)
def display_error_message(self, message):
QMessageBox.critical(None, "Error", message)
def resource_path(relative_path: str) -> str:
if hasattr(sys, '_MEIPASS'):
base_path = getattr(sys, "_MEIPASS")
else:
base_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
return os.path.join(base_path, relative_path)

View File

@@ -1,33 +0,0 @@
from pynput.keyboard import Listener
from .util import name_to_key
class KeyCombinationListener:
"""
Simple and bad key combination listener.
"""
def __init__(self):
self.current_keys = set()
self.callbacks = {}
self.listener = Listener(on_press=self.on_key_press, on_release=self.on_key_release)
def add_comb(self, keys, callback):
self.callbacks[tuple([name_to_key(key_name) for key_name in sorted(keys)])] = callback
def on_key_press(self, key):
self.current_keys.add(key)
for comb, callback in self.callbacks.items():
if all(k in self.current_keys for k in comb):
return callback()
def on_key_release(self, key):
if key in self.current_keys:
self.current_keys.remove(key)
def start(self):
self.listener.start()
def stop(self):
self.listener.stop()

View File

@@ -1,60 +0,0 @@
import json
import os
import uuid
from datetime import datetime
from platform import uname
from screeninfo import get_monitors
class MetadataManager:
"""
Handles various system metadata collection.
"""
def __init__(self, recording_path: str, natural_scrolling: bool):
self.recording_path = recording_path
self.metadata = uname()._asdict()
self.metadata["id"] = uuid.getnode()
main_monitor = get_monitors()[0]
self.metadata["screen_width"] = main_monitor.width
self.metadata["screen_height"] = main_monitor.height
try:
match self.metadata["system"]:
case "Windows":
import wmi
for item in wmi.WMI().Win32_ComputerSystem():
self.metadata["model"] = item.Model
break
case "Darwin":
import subprocess
model = subprocess.check_output(["sysctl", "-n", "hw.model"]).decode().strip()
self.metadata["model"] = model
case "Linux":
with open("/sys/devices/virtual/dmi/id/product_name", "r") as f:
self.metadata["model"] = f.read().strip()
except:
self.metadata["model"] = "Unknown"
self.metadata["scroll_direction"] = -1 if natural_scrolling else 1
def save_metadata(self):
metadata_path = os.path.join(self.recording_path, "metadata.json")
with open(metadata_path, "w") as f:
json.dump(self.metadata, f, indent=4)
def collect(self):
self.metadata["start_time"] = self._get_time_stamp()
def end_collect(self):
self.metadata["stop_time"] = self._get_time_stamp()
def add_obs_record_state_timings(self, record_state_events: dict[str, float]):
self.metadata["obs_record_state_timings"] = record_state_events
def _get_time_stamp(self):
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

View File

@@ -1,200 +0,0 @@
import os
import subprocess
import time
from platform import system
import obsws_python as obs
import psutil
def is_obs_running() -> bool:
try:
for process in psutil.process_iter(attrs=["pid", "name"]):
if "obs" in process.info["name"].lower():
return True
return False
except:
raise Exception("Could not check if OBS is running already. Please check manually.")
def close_obs(obs_process: subprocess.Popen):
if obs_process:
obs_process.terminate()
try:
obs_process.wait(timeout=5)
except subprocess.TimeoutExpired:
obs_process.kill()
def find_obs() -> str:
common_paths = {
"Windows": [
"C:\\Program Files\\obs-studio\\bin\\64bit\\obs64.exe",
"C:\\Program Files (x86)\\obs-studio\\bin\\32bit\\obs32.exe"
],
"Darwin": [
"/Applications/OBS.app/Contents/MacOS/OBS",
"/opt/homebrew/bin/obs"
],
"Linux": [
"/usr/bin/obs",
"/usr/local/bin/obs"
]
}
for path in common_paths.get(system(), []):
if os.path.exists(path):
return path
try:
if system() == "Windows":
obs_path = subprocess.check_output("where obs", shell=True).decode().strip()
else:
obs_path = subprocess.check_output("which obs", shell=True).decode().strip()
if os.path.exists(obs_path):
return obs_path
except subprocess.CalledProcessError:
pass
return "obs"
def open_obs() -> subprocess.Popen:
try:
obs_path = find_obs()
if system() == "Windows":
# you have to change the working directory first for OBS to find the correct locale on windows
os.chdir(os.path.dirname(obs_path))
obs_path = os.path.basename(obs_path)
return subprocess.Popen([obs_path, "--startreplaybuffer", "--minimize-to-tray"])
except:
raise Exception("Failed to find OBS, please open OBS manually.")
class OBSClient:
"""
Controls the OBS client via the OBS websocket.
Sets all the correct settings for recording.
"""
def __init__(
self,
recording_path: str,
metadata: dict,
fps=30,
output_width=1280,
output_height=720,
):
self.metadata = metadata
self.req_client = obs.ReqClient()
self.event_client = obs.EventClient()
self.record_state_events = {}
def on_record_state_changed(data):
output_state = data.output_state
print("record state changed:", output_state)
if output_state not in self.record_state_events:
self.record_state_events[output_state] = []
self.record_state_events[output_state].append(time.perf_counter())
self.event_client.callback.register(on_record_state_changed)
self.old_profile = self.req_client.get_profile_list().current_profile_name
if "computer_tracker" not in self.req_client.get_profile_list().profiles:
self.req_client.create_profile("computer_tracker")
else:
self.req_client.set_current_profile("computer_tracker")
self.req_client.create_profile("temp")
self.req_client.remove_profile("temp")
self.req_client.set_current_profile("computer_tracker")
base_width = metadata["screen_width"]
base_height = metadata["screen_height"]
if metadata["system"] == "Darwin":
# for retina displays
# TODO: check if external displays are messed up by this
base_width *= 2
base_height *= 2
scaled_width, scaled_height = _scale_resolution(base_width, base_height, output_width, output_height)
self.req_client.set_profile_parameter("Video", "BaseCX", str(base_width))
self.req_client.set_profile_parameter("Video", "BaseCY", str(base_height))
self.req_client.set_profile_parameter("Video", "OutputCX", str(scaled_width))
self.req_client.set_profile_parameter("Video", "OutputCY", str(scaled_height))
self.req_client.set_profile_parameter("Video", "ScaleType", "lanczos")
self.req_client.set_profile_parameter("AdvOut", "RescaleRes", f"{base_width}x{base_height}")
self.req_client.set_profile_parameter("AdvOut", "RecRescaleRes", f"{base_width}x{base_height}")
self.req_client.set_profile_parameter("AdvOut", "FFRescaleRes", f"{base_width}x{base_height}")
self.req_client.set_profile_parameter("Video", "FPSCommon", str(fps))
self.req_client.set_profile_parameter("Video", "FPSInt", str(fps))
self.req_client.set_profile_parameter("Video", "FPSNum", str(fps))
self.req_client.set_profile_parameter("Video", "FPSDen", "1")
self.req_client.set_profile_parameter("SimpleOutput", "RecFormat2", "mp4")
bitrate = int(_get_bitrate_mbps(scaled_width, scaled_height, fps=fps) * 1000 / 50) * 50
self.req_client.set_profile_parameter("SimpleOutput", "VBitrate", str(bitrate))
# do this in order to get pause & resume
self.req_client.set_profile_parameter("SimpleOutput", "RecQuality", "Small")
self.req_client.set_profile_parameter("SimpleOutput", "FilePath", recording_path)
# TODO: not all OBS configs have this, maybe just instruct the user to mute themselves
try:
self.req_client.set_input_mute("Mic/Aux", muted=True)
except obs.error.OBSSDKRequestError :
# In case there is no Mic/Aux input, this will throw an error
pass
def start_recording(self):
self.req_client.start_record()
def stop_recording(self):
self.req_client.stop_record()
self.req_client.set_current_profile(self.old_profile) # restore old profile
def pause_recording(self):
self.req_client.pause_record()
def resume_recording(self):
self.req_client.resume_record()
def _get_bitrate_mbps(width: int, height: int, fps=30) -> float:
"""
Gets the YouTube recommended bitrate in Mbps for a given resolution and framerate.
Refer to https://support.google.com/youtube/answer/1722171?hl=en#zippy=%2Cbitrate
"""
resolutions = {
(7680, 4320): {30: 120, 60: 180},
(3840, 2160): {30: 40, 60: 60.5},
(2160, 1440): {30: 16, 60: 24},
(1920, 1080): {30: 8, 60: 12},
(1280, 720): {30: 5, 60: 7.5},
(640, 480): {30: 2.5, 60: 4},
(480, 360): {30: 1, 60: 1.5}
}
if (width, height) in resolutions:
return resolutions[(width, height)].get(fps)
else:
# approximate the bitrate using a simple linear model
area = width * height
multiplier = 3.5982188179592543e-06 if fps == 30 else 5.396175171097084e-06
constant = 2.418399836285939 if fps == 30 else 3.742780056500365
return multiplier * area + constant
def _scale_resolution(base_width: int, base_height: int, target_width: int, target_height: int) -> tuple[int, int]:
target_area = target_width * target_height
aspect_ratio = base_width / base_height
scaled_height = int((target_area / aspect_ratio) ** 0.5)
scaled_width = int(aspect_ratio * scaled_height)
return scaled_width, scaled_height

View File

@@ -1,188 +0,0 @@
import json
import math
import os
import sys
import time
import pyautogui
from pynput.keyboard import Controller as KeyboardController
from pynput.keyboard import Key
from pynput.mouse import Button
from pynput.mouse import Controller as MouseController
from .keycomb import KeyCombinationListener
from .util import (fix_windows_dpi_scaling, get_recordings_dir, name_to_button,
name_to_key)
pyautogui.PAUSE = 0
pyautogui.DARWIN_CATCH_UP_TIME = 0
class Player:
"""
Plays back recordings.
"""
def __init__(self):
self.stop_playback = False
self.listener = KeyCombinationListener()
def stop_comb_pressed():
self.stop_playback = True
return False
self.listener.add_comb(("shift", "esc"), stop_comb_pressed)
self.listener.start()
def play(self, recording_path: str):
with open(os.path.join(recording_path, "events.jsonl"), "r") as f:
events = [json.loads(line) for line in f.readlines()]
with open(os.path.join(recording_path, "metadata.json"), "r") as f:
metadata = json.load(f)
self.playback(events, metadata)
def playback(self, events: list[dict], metadata: dict):
if metadata["system"] == "Windows":
fix_windows_dpi_scaling()
mouse_controller = MouseController()
keyboard_controller = KeyboardController()
if not events:
self.listener.stop()
return
presses_to_skip = 0
releases_to_skip = 0
in_click_sequence = False
for i, event in enumerate(events):
start_time = time.perf_counter()
if self.stop_playback:
return
def do_mouse_press(button):
for j, second_event in enumerate(events[i+1:]):
# make sure the time between mouse clicks is less than 500ms
if second_event["time_stamp"] - event["time_stamp"] > 0.5:
break
if "x" in second_event and "y" in second_event:
# if the mouse moves out of the click radius/rectangle, it is not a click sequence
if math.sqrt((second_event["y"] - event["y"]) ** 2 +
(second_event["x"] - event["x"]) ** 2) > 4:
break
if second_event["action"] == "click" and second_event["pressed"]:
for k, third_event in enumerate(events[i+j+2:]):
if third_event["time_stamp"] - second_event["time_stamp"] > 0.5:
break
if "x" in third_event and "y" in third_event:
if math.sqrt((third_event["y"] - event["y"]) ** 2 +
(third_event["x"] - event["x"]) ** 2) > 5:
break
if third_event["action"] == "click" and third_event["pressed"]:
mouse_controller.click(button, 3)
return 2, 2
mouse_controller.click(button, 2)
return 1, 1
mouse_controller.press(button)
return 0, 0
if event["action"] == "move":
mouse_controller.position = (event["x"], event["y"])
elif event["action"] == "click":
button = name_to_button(event["button"])
if event["pressed"]:
if presses_to_skip == 0:
presses, releases = do_mouse_press(button)
presses_to_skip += presses
releases_to_skip += releases
if presses > 0:
in_click_sequence = True
else:
presses_to_skip -= 1
else:
if releases_to_skip == 0:
mouse_controller.release(button)
if in_click_sequence:
keyboard_controller.press(Key.shift)
mouse_controller.click(Button.left)
keyboard_controller.release(Key.shift)
in_click_sequence = False
else:
releases_to_skip -= 1
elif event["action"] == "scroll":
if metadata["system"] == "Windows":
# for some reason on windows, pynput scroll is correct but pyautogui is not
mouse_controller.scroll(metadata["scroll_direction"] * event["dx"], metadata["scroll_direction"] * event["dy"])
else:
pyautogui.hscroll(clicks=metadata["scroll_direction"] * event["dx"])
pyautogui.vscroll(clicks=metadata["scroll_direction"] * event["dy"])
elif event["action"] in ["press", "release"]:
key = name_to_key(event["name"])
if event["action"] == "press":
keyboard_controller.press(key)
else:
keyboard_controller.release(key)
# sleep for the correct amount of time
end_time = time.perf_counter()
execution_time = end_time - start_time
if i + 1 < len(events):
desired_delay = events[i + 1]["time_stamp"] - event["time_stamp"]
delay = desired_delay - execution_time
if delay < 0:
print(f"warning: behind by {-delay * 1000:.3f} ms")
elif delay != 0:
wait_until = time.perf_counter() + delay
while time.perf_counter() < wait_until:
pass
self.listener.stop()
def get_latest_recording() -> str:
recordings_dir = get_recordings_dir()
if not os.path.exists(recordings_dir):
raise Exception("The recordings directory does not exist")
recordings = [os.path.join(recordings_dir, f) for f in os.listdir(recordings_dir) if os.path.isdir(os.path.join(recordings_dir, f))]
if len(recordings) == 0:
raise Exception("You have no recordings to play back")
latest_recording = max(recordings, key=os.path.getctime)
return latest_recording
def main():
player = Player()
if len(sys.argv) > 1:
recording_path = sys.argv[1]
else:
recording_path = get_latest_recording()
player.play(recording_path)
if __name__ == "__main__":
n = 3
print("press shift+esc to stop the playback")
print(f"starting in {n} seconds...")
time.sleep(n)
main()

View File

@@ -1,145 +0,0 @@
import json
import os
import time
from datetime import datetime
from platform import system
from queue import Queue
from pynput import keyboard, mouse
from pynput.keyboard import KeyCode
from PyQt6.QtCore import QThread, pyqtSignal
from .metadata import MetadataManager
from .obs_client import OBSClient
from .util import fix_windows_dpi_scaling, get_recordings_dir
class Recorder(QThread):
"""
Makes recordings.
"""
recording_stopped = pyqtSignal()
def __init__(self, natural_scrolling: bool):
super().__init__()
if system() == "Windows":
fix_windows_dpi_scaling()
self.recording_path = self._get_recording_path()
self._is_recording = False
self._is_paused = False
self.event_queue = Queue()
self.events_file = open(os.path.join(self.recording_path, "events.jsonl"), "a")
self.metadata_manager = MetadataManager(
recording_path=self.recording_path,
natural_scrolling=natural_scrolling
)
self.obs_client = OBSClient(recording_path=self.recording_path,
metadata=self.metadata_manager.metadata)
self.mouse_listener = mouse.Listener(
on_move=self.on_move,
on_click=self.on_click,
on_scroll=self.on_scroll)
self.keyboard_listener = keyboard.Listener(
on_press=self.on_press,
on_release=self.on_release)
def on_move(self, x, y):
if not self._is_paused:
self.event_queue.put({"time_stamp": time.perf_counter(),
"action": "move",
"x": x,
"y": y}, block=False)
def on_click(self, x, y, button, pressed):
if not self._is_paused:
self.event_queue.put({"time_stamp": time.perf_counter(),
"action": "click",
"x": x,
"y": y,
"button": button.name,
"pressed": pressed}, block=False)
def on_scroll(self, x, y, dx, dy):
if not self._is_paused:
self.event_queue.put({"time_stamp": time.perf_counter(),
"action": "scroll",
"x": x,
"y": y,
"dx": dx,
"dy": dy}, block=False)
def on_press(self, key):
if not self._is_paused:
self.event_queue.put({"time_stamp": time.perf_counter(),
"action": "press",
"name": key.char if type(key) == KeyCode else key.name}, block=False)
def on_release(self, key):
if not self._is_paused:
self.event_queue.put({"time_stamp": time.perf_counter(),
"action": "release",
"name": key.char if type(key) == KeyCode else key.name}, block=False)
def run(self):
self._is_recording = True
self.metadata_manager.collect()
self.obs_client.start_recording()
self.mouse_listener.start()
self.keyboard_listener.start()
while self._is_recording:
event = self.event_queue.get()
self.events_file.write(json.dumps(event) + "\n")
def stop_recording(self):
if self._is_recording:
self._is_recording = False
self.metadata_manager.end_collect()
self.mouse_listener.stop()
self.keyboard_listener.stop()
self.obs_client.stop_recording()
self.metadata_manager.add_obs_record_state_timings(self.obs_client.record_state_events)
self.events_file.close()
self.metadata_manager.save_metadata()
self.recording_stopped.emit()
def pause_recording(self):
if not self._is_paused and self._is_recording:
self._is_paused = True
self.obs_client.pause_recording()
self.event_queue.put({"time_stamp": time.perf_counter(),
"action": "pause"}, block=False)
def resume_recording(self):
if self._is_paused and self._is_recording:
self._is_paused = False
self.obs_client.resume_recording()
self.event_queue.put({"time_stamp": time.perf_counter(),
"action": "resume"}, block=False)
def _get_recording_path(self) -> str:
recordings_dir = get_recordings_dir()
if not os.path.exists(recordings_dir):
os.mkdir(recordings_dir)
current_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
recording_path = os.path.join(recordings_dir, f"recording-{current_time}")
os.mkdir(recording_path)
return recording_path

View File

@@ -1,38 +0,0 @@
import os
import platform
import subprocess
from pathlib import Path
from pynput.keyboard import Key, KeyCode
from pynput.mouse import Button
def name_to_key(name: str) -> Key | KeyCode:
try:
return getattr(Key, name)
except AttributeError:
return KeyCode.from_char(name)
def name_to_button(name: str) -> Button:
return getattr(Button, name)
def get_recordings_dir() -> str:
documents_folder = Path.home() / 'Documents' / 'DuckTrack_Recordings'
return str(documents_folder)
def fix_windows_dpi_scaling():
"""
Fixes DPI scaling issues with legacy windows applications
Reference: https://pynput.readthedocs.io/en/latest/mouse.html#ensuring-consistent-coordinates-between-listener-and-controller-on-windows
"""
import ctypes
PROCESS_PER_MONITOR_DPI_AWARE = 2
ctypes.windll.shcore.SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE)
def open_file(path):
if platform.system() == "Windows":
os.startfile(path)
elif platform.system() == "Darwin":
subprocess.Popen(["open", path])
else:
subprocess.Popen(["xdg-open", path])

View File

@@ -1,48 +0,0 @@
import glob
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from scipy.stats import sem, t
def calculate_confidence_interval(data, confidence=0.95):
n = len(data)
m = np.mean(data)
std_err = sem(data)
h = std_err * t.ppf((1 + confidence) / 2, n - 1)
return m, m-h, m+h
runs = glob.glob("run*.txt")
TOTAL_EVENTS = 22509
percent_delays = []
all_delays = []
for run in runs:
with open(run, "r") as f:
delays = [float(line.split()[3]) for line in f if float(line.split()[3]) > 0] # consider only positive delays
percent_delays.append((len(delays) / TOTAL_EVENTS) * 100)
all_delays.extend(delays)
average_percent_delays = np.mean(percent_delays)
confidence_interval_percent_delays = calculate_confidence_interval(percent_delays)
print(f"Average percentage of delayed events across all runs: {average_percent_delays:.2f}%")
print(f"95% Confidence interval: ({confidence_interval_percent_delays[1]:.2f}%, {confidence_interval_percent_delays[2]:.2f}%)")
if all_delays:
mean_delay = np.mean(all_delays)
confidence_interval_delays = calculate_confidence_interval(all_delays)
print(f"Mean delay time: {mean_delay:.2f}")
print(f"95% Confidence interval for delay time: ({confidence_interval_delays[1]:.2f}, {confidence_interval_delays[2]:.2f})")
else:
print("No delay data available for calculation.")
sns.histplot(all_delays, bins=30, kde=False)
plt.xlabel('Delay Time (ms)')
plt.ylabel('Frequency')
plt.yscale('log')
plt.title('Histogram of Delay Times (macOS)')
plt.savefig('delays.png', dpi=300)
plt.show()

View File

@@ -1,110 +0,0 @@
import glob
import os
import cv2
import matplotlib.pyplot as plt
import numpy as np
import scipy.stats as stats
from skimage.metrics import structural_similarity as ssim
from tqdm import tqdm
# use this: https://sketch.io
def calculate_rmse(imageA, imageB):
err = np.sum((imageA - imageB) ** 2)
err /= float(imageA.shape[0] * imageA.shape[1])
return np.sqrt(err)
def compare_images(ground_truth_path, sample_paths):
results = []
gt_image = cv2.imread(ground_truth_path, cv2.IMREAD_GRAYSCALE)
if gt_image is None:
raise ValueError("Ground truth image could not be read. Please check the file path.")
gt_image = gt_image.astype("float") / 255.0
for path in tqdm(sample_paths):
sample_image = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
if sample_image is None:
print(f"WARNING: Sample image at path {path} could not be read. Skipping this image.")
continue
sample_image = sample_image.astype("float") / 255.0
rmse_value = calculate_rmse(gt_image, sample_image)
ssim_value, _ = ssim(gt_image, sample_image, full=True, data_range=1) # Corrected line
diff_mask = cv2.absdiff(gt_image, sample_image)
# plt.imshow(diff_mask * 255, cmap='gray')
# plt.title(f'Difference Mask for {os.path.basename(path)}\nRMSE: {rmse_value:.5f} - SSIM: {ssim_value:.5f}')
# plt.show()
results.append({
'path': path,
'rmse': rmse_value,
'ssim': ssim_value,
'diff_mask': diff_mask
})
return results
ground_truth = 'ground_truth.png'
sample_images = glob.glob("samples/*.png")
results = compare_images(ground_truth, sample_images)
for res in results:
print(f"Image: {res['path']} - RMSE: {res['rmse']} - SSIM: {res['ssim']}")
def calculate_confidence_interval(data, confidence_level=0.95):
mean = np.mean(data)
sem = stats.sem(data)
df = len(data) - 1
me = sem * stats.t.ppf((1 + confidence_level) / 2, df)
return mean - me, mean + me
rmse_values = [res['rmse'] for res in results]
ssim_values = [res['ssim'] for res in results]
rmse_mean = np.mean(rmse_values)
rmse_median = np.median(rmse_values)
rmse_stdev = np.std(rmse_values, ddof=1)
ssim_mean = np.mean(ssim_values)
ssim_median = np.median(ssim_values)
ssim_stdev = np.std(ssim_values, ddof=1)
rmse_ci = calculate_confidence_interval(rmse_values)
ssim_ci = calculate_confidence_interval(ssim_values)
print(f"\nRMSE - Mean: {rmse_mean}, Median: {rmse_median}, Std Dev: {rmse_stdev}, 95% CI: {rmse_ci}")
print(f"SSIM - Mean: {ssim_mean}, Median: {ssim_median}, Std Dev: {ssim_stdev}, 95% CI: {ssim_ci}")
print(f"RMSE: {rmse_mean} ± {rmse_ci[1] - rmse_mean}")
print(f"SSIM: {ssim_mean} ± {ssim_ci[1] - ssim_mean}")
def save_average_diff_map(results, save_path='average_diff_map.png'):
if not results:
print("No results available to create an average diff map.")
return
avg_diff_map = None
for res in results:
if avg_diff_map is None:
avg_diff_map = np.zeros_like(res['diff_mask'])
avg_diff_map += res['diff_mask']
avg_diff_map /= len(results)
avg_diff_map = (avg_diff_map * 255).astype(np.uint8)
cv2.imwrite(save_path, avg_diff_map)
# Usage
save_average_diff_map(results)

View File

@@ -1,4 +0,0 @@
success = 10
total = 10
print(success / total)

View File

@@ -1,48 +0,0 @@
import csv
import time
import numpy as np
from tqdm import tqdm
def check_sleep(duration, sleep_function):
start = time.perf_counter()
sleep_function(duration)
end = time.perf_counter()
elapsed = end - start
return abs(elapsed - duration)
def busy_sleep(duration):
end_time = time.perf_counter() + duration
while time.perf_counter() < end_time:
pass
def measure_accuracy(sleep_function, durations, iterations=100):
average_errors = []
for duration in tqdm(durations):
errors = [check_sleep(duration, sleep_function) for _ in range(iterations)]
average_error = np.mean(errors)
average_errors.append(average_error)
return average_errors
durations = np.arange(0.001, 0.101, 0.001) # From 1ms to 100ms in 1ms increments
iterations = 100
sleep_errors = measure_accuracy(time.sleep, durations, iterations)
busy_sleep_errors = measure_accuracy(busy_sleep, durations, iterations)
def save_to_csv(filename, durations, sleep_errors, busy_sleep_errors):
with open(filename, 'w', newline='') as csvfile:
fieldnames = ['duration', 'sleep_error', 'busy_sleep_error']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for duration, sleep_error, busy_sleep_error in zip(durations, sleep_errors, busy_sleep_errors):
writer.writerow({
'duration': duration,
'sleep_error': sleep_error,
'busy_sleep_error': busy_sleep_error
})
print("Data saved to", filename)
save_to_csv('sleep_data.csv', durations * 1000, np.array(sleep_errors) * 1000, np.array(busy_sleep_errors) * 1000)

View File

@@ -1,33 +0,0 @@
import csv
import matplotlib.pyplot as plt
def plot_from_csv(filename, save_plot=False):
durations = []
sleep_errors = []
busy_sleep_errors = []
with open(filename, 'r') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
durations.append(float(row['duration']))
sleep_errors.append(float(row['sleep_error']))
busy_sleep_errors.append(float(row['busy_sleep_error']))
plt.figure(figsize=(10, 5))
plt.plot(durations, sleep_errors, label='time.sleep()', marker='o')
plt.plot(durations, busy_sleep_errors, label='busy_sleep()', marker='x')
plt.xlabel('Desired Delay (ms)')
plt.ylabel('Average Error (ms)')
plt.title('Sleep Accuracy: time.sleep() vs Busy-Wait Loop (macOS)')
plt.legend()
plt.grid(True)
if save_plot:
plt.savefig('sleep_accuracy_plot.png', dpi=300)
print("Plot saved as sleep_accuracy_plot.png")
plt.show()
plot_from_csv('sleep_data.csv', save_plot=True)

View File

@@ -1,110 +0,0 @@
import glob
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import scipy.stats as stats
import seaborn as sns
# use this: https://www.estopwatch.net/
def read_file(file_path):
df = pd.read_csv(file_path)
df['Elapsed time'] = pd.to_datetime(df['Elapsed time'], errors='coerce')
return df
def analyze_new_error(run_df, groundtruth_df):
cumulative_errors = run_df['Elapsed time'] - groundtruth_df['Elapsed time']
cumulative_errors_in_seconds = cumulative_errors.dt.total_seconds()
new_errors_in_seconds = cumulative_errors_in_seconds.diff().fillna(cumulative_errors_in_seconds[0])
new_error_points = new_errors_in_seconds[new_errors_in_seconds != 0].index.tolist()
return new_errors_in_seconds[new_error_points]
def calculate_statistics(errors):
if len(errors) == 0:
return {
'mean_error': 0,
'median_error': 0,
'stddev_error': 0,
'rmse_error': 0,
'confidence_interval': (0, 0),
'error_frequency': 0
}
mean_error = np.mean(errors)
median_error = np.median(errors)
stddev_error = np.std(errors)
rmse_error = np.sqrt(np.mean(np.square(errors)))
ci_low, ci_high = stats.t.interval(
confidence=0.95,
df=len(errors) - 1,
loc=mean_error,
scale=stats.sem(errors) if len(errors) > 1 else 0
)
return {
'mean_error': mean_error,
'median_error': median_error,
'stddev_error': stddev_error,
'rmse_error': rmse_error,
'confidence_interval': (ci_low, ci_high),
}
def main():
groundtruth_file = 'groundtruth.csv'
run_files = glob.glob('runs/*.csv')
groundtruth_df = read_file(groundtruth_file)
run_dfs = {f'run{i+1}': read_file(file) for i, file in enumerate(run_files)}
total_errors = []
total_points = 0
all_errors = []
for run, df in run_dfs.items():
errors = analyze_new_error(df, groundtruth_df)
total_errors.extend(errors)
all_errors.extend(errors)
total_points += len(df)
results = calculate_statistics(errors)
error_frequency = len(errors) / len(df)
print(f"Results for {run}:")
print(f"Mean New Error: {results['mean_error']:.5f} seconds")
print(f"Median New Error: {results['median_error']:.5f} seconds")
print(f"Standard Deviation of New Error: {results['stddev_error']:.5f} seconds")
print(f"RMSE of New Error: {results['rmse_error']:.5f} seconds")
print(f"95% Confidence Interval of New Error: ({results['confidence_interval'][0]:.5f}, {results['confidence_interval'][1]:.5f}) seconds")
print(f"New Error Frequency: {error_frequency*100:.5f} %")
print('-----------------------------------------')
total_results = calculate_statistics(total_errors)
total_error_frequency = len(total_errors) / total_points
print("Total Statistics:")
print(f"Mean New Error: {total_results['mean_error']:.5f} seconds")
print(f"Median New Error: {total_results['median_error']:.5f} seconds")
print(f"Standard Deviation of New Error: {total_results['stddev_error']:.5f} seconds")
print(f"RMSE of New Error: {total_results['rmse_error']:.5f} seconds")
print(f"95% Confidence Interval of New Error: ({total_results['confidence_interval'][0]:.5f}, {total_results['confidence_interval'][1]:.5f}) seconds")
print(f"New Error Frequency: {total_error_frequency*100:.5f} %")
# do plus minus
print(f"New Error: {total_results['mean_error']:.5f} ± {total_results['confidence_interval'][1] - total_results['mean_error']:.5f} seconds")
plt.figure(figsize=(10, 5))
sns.histplot(all_errors, bins=12, kde=False)
plt.title('Distribution of Newly Introduced Errors (macOS)')
plt.xlabel('Error Duration (seconds)')
plt.ylabel('Frequency')
plt.savefig('error_dist', dpi=300)
plt.show()
if __name__ == "__main__":
main()

View File

@@ -1,39 +0,0 @@
import signal
import sys
import traceback
from PyQt6.QtWidgets import QApplication
from ducktrack import MainInterface
def main():
app = QApplication(sys.argv)
app.setQuitOnLastWindowClosed(False)
signal.signal(signal.SIGINT, signal.SIG_DFL)
interface = MainInterface(app)
interface.show()
# TODO: come up with a better error solution to this
original_excepthook = sys.excepthook
def handle_exception(exc_type, exc_value, exc_traceback):
print("Exception type:", exc_type)
print("Exception value:", exc_value)
trace_details = traceback.format_exception(exc_type, exc_value, exc_traceback)
trace_string = "".join(trace_details)
print("Exception traceback:", trace_string)
message = f"An error occurred!\n\n{exc_value}\n\n{trace_string}"
interface.display_error_message(message)
original_excepthook(exc_type, exc_value, exc_traceback)
sys.excepthook = handle_exception
sys.exit(app.exec())
if __name__ == "__main__":
main()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 836 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,9 +0,0 @@
git+https://github.com/moses-palmer/pynput.git@refs/pull/541/head # to make sure that it works on Apple Silicon
pyautogui
obsws-python
PyQt6
Pillow
screeninfo
wmi
psutil
pyinstaller

View File

@@ -1,12 +1,16 @@
# Setup Instructions
# Evaluator Setup Details
Setup scaffolding for the evaluators in the desktop environment for those who want to know the details of the evaluator setup for customized evaluation and extension
## Overall
Disable the system crash report by:
Inside the virtual machine, disable the system crash report by:
```
sudo vim /etc/default/apport
```
and then change the `enabled` to `0`.
## VSCode
todo
## LibreOffice
For LibreOffice, please enter into the app first, and then enable the no pop-up when 'ctrl + s'.

View File

@@ -558,39 +558,54 @@ def get_active_url_from_accessTree(env, config):
Return
url: str
"""
accessibility_tree: str = env.controller.get_accessibility_tree()
# download accessibility tree to "/home/user/Desktop"
logger.debug("AT@eval: %s", accessibility_tree)
# first, use accessibility API to get the active tab URL
at: _Element = lxml.etree.fromstring(accessibility_tree)
arch = platform.machine()
print("Your architecture is: {}".format(arch))
try:
if "arm" in arch:
selector = CSSSelector("application[name=Chromium] entry[name=Address\\ and\\ search\\ bar]",
namespaces=_accessibility_ns_map)
else:
selector = CSSSelector("application[name=Google\\ Chrome] entry[name=Address\\ and\\ search\\ bar]",
namespaces=_accessibility_ns_map)
except:
logger.error("Failed to parse the selector for active tab URL")
return None
elements: List[_Element] = selector(at)
# if "xpath" in config:
# elements: List[_Element] = at.xpath(config["xpath"], namespaces=_accessibility_ns_map)
# elif "selectors" in config:
# selector = CSSSelector(", ".join(config["selectors"]), namespaces=_accessibility_ns_map)
# elements: List[_Element] = selector(at)
if len(elements) == 0:
print("no elements found")
return None
elif elements[-1].text is None:
print("no text found")
# Ensure the controller and its method are accessible and return a valid result
if hasattr(env, 'controller') and callable(getattr(env.controller, 'get_accessibility_tree', None)):
accessibility_tree = env.controller.get_accessibility_tree()
if accessibility_tree is None:
print("Failed to get the accessibility tree.")
return None
else:
print("Controller or method 'get_accessibility_tree' not found.")
return None
active_tab_url = config["goto_prefix"] + elements[0].text if "goto_prefix" in config.keys() else "https://" + \
elements[0].text
print("active tab url now: {}".format(active_tab_url))
logger.debug("AT@eval: %s", accessibility_tree)
at = None
try:
at = lxml.etree.fromstring(accessibility_tree)
except ValueError as e:
logger.error(f"Error parsing accessibility tree: {e}")
return None
# Determine the correct selector based on system architecture
selector = None
arch = platform.machine()
print(f"Your architecture is: {arch}")
if "arm" in arch:
selector_string = "application[name=Chromium] entry[name=Address\\ and\\ search\\ bar]"
else:
selector_string = "application[name=Google\\ Chrome] entry[name=Address\\ and\\ search\\ bar]"
try:
selector = CSSSelector(selector_string, namespaces=_accessibility_ns_map)
except Exception as e:
logger.error(f"Failed to parse the selector for active tab URL: {e}")
return None
elements = selector(at) if selector else []
if not elements:
print("No elements found.")
return None
elif not elements[-1].text:
print("No text found in the latest element.")
return None
# Use a default prefix if 'goto_prefix' is not specified in the config
goto_prefix = config.get("goto_prefix", "https://")
active_tab_url = f"{goto_prefix}{elements[0].text}"
print(f"Active tab url now: {active_tab_url}")
return active_tab_url
@@ -1079,18 +1094,18 @@ def get_data_delete_automacally(env, config: Dict[str, str]):
def get_active_tab_html_parse(env, config: Dict[str, Any]):
"""
This function is used to get the specific element's text content from the active tab's html.
config:
config:
Dict[str, str]{
# Keys used in get_active_url_from_accessTree: "xpath", "selectors"
'category':
'category':
choose from ["class", "label", "xpath", "input"], used to indicate how to find the element
'labelObject':
'labelObject':
only exists when category is "label",
a dict like { "labelSelector": "the key you want to store the text content of this label's ee=lement"}
'class_singleObject':
only exists when category is "class", a dict with keys as the class name,
'class_singleObject':
only exists when category is "class", a dict with keys as the class name,
like { "class name" : "the key you want to store the text content of this element" }
'class_multiObject':
'class_multiObject':
only exists when category is "class", used for elements with same class name.
Two layer of dict, like
( {
@@ -1099,8 +1114,8 @@ def get_active_tab_html_parse(env, config: Dict[str, Any]):
...
}
} )
'xpathObject':
only exists when category is "xpath", a dict with keys as the xpath,
'xpathObject':
only exists when category is "xpath", a dict with keys as the xpath,
like { "full xpath" : "the key you want to store the text content of this element" }
'inputObject':
only exists when category is "input",
@@ -1151,32 +1166,50 @@ def get_active_tab_html_parse(env, config: Dict[str, Any]):
if target_page is None:
logger.error("Your tab is not the target tab.")
return {}
return_json = {}
def safely_get_text_content(selector):
elements = target_page.query_selector_all(selector)
return [element.text_content().strip() for element in elements if element]
if config["category"] == "class":
# find the text of elements in html with specific class name
class_multiObject = config["class_multiObject"]
for key in class_multiObject.keys():
object_dict = class_multiObject[key]
for order_key in object_dict.keys():
return_json[object_dict[order_key]] = target_page.query_selector_all("." + key)[
int(order_key)].text_content().strip()
class_singleObject = config["class_singleObject"]
for key in class_singleObject.keys():
return_json[class_singleObject[key]] = target_page.query_selector("." + key).text_content().strip()
class_multiObject = config.get("class_multiObject", {})
for class_name, object_dict in class_multiObject.items():
elements_texts = safely_get_text_content("." + class_name)
for order_key, key in object_dict.items():
index = int(order_key)
if len(elements_texts) > index:
return_json[key] = elements_texts[index]
class_singleObject = config.get("class_singleObject", {})
for class_name, key in class_singleObject.items():
element_text = safely_get_text_content("." + class_name)
if element_text:
return_json[key] = element_text[0]
elif config['category'] == "label":
# find the text of elements in html with specific label name
labelObject = config["labelObject"]
for key in labelObject.keys():
return_json[labelObject[key]] = target_page.get_by_label(key).text_content().strip()
# Assuming get_by_label is a custom function or part of the framework being used
labelObject = config.get("labelObject", {})
for labelSelector, key in labelObject.items():
text = target_page.locator(f"text={labelSelector}").first.text_content().strip()
if text:
return_json[key] = text
elif config["category"] == "xpath":
# find the text of elements in html with specific xpath
xpathObject = config["xpathObject"]
for key in xpathObject.keys():
return_json[xpathObject[key]] = target_page.locator("xpath=" + key).text_content().strip()
xpathObject = config.get("xpathObject", {})
for xpath, key in xpathObject.items():
elements = target_page.locator(f"xpath={xpath}")
if elements.count() > 0:
return_json[key] = elements.first.text_content().strip()
elif config["category"] == "input":
inputObject = config["inputObject"]
for key in inputObject.keys():
return_json[inputObject[key]] = target_page.locator("xpath=" + key).input_value().strip()
inputObjects = config.get("inputObject", {})
for xpath, key in inputObjects.items():
inputs = target_page.locator(f"xpath={xpath}")
if inputs.count() > 0:
return_json[key] = inputs.first.input_value().strip()
browser.close()
return return_json

View File

@@ -6,6 +6,7 @@ import zipfile
from io import BytesIO
from typing import List, Dict, Any
import easyocr
from PIL import Image
from docx import Document
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT, WD_TAB_ALIGNMENT
@@ -247,8 +248,9 @@ def compare_docx_images(docx_file1, docx_file2):
return 1
import easyocr
def compare_image_text(image_path, rule):
if not image_path:
return 0
reader = easyocr.Reader(['en'])
result = reader.readtext(image_path)
extracted_text = ' '.join([entry[1] for entry in result])

View File

@@ -20,7 +20,7 @@ from lxml.cssselect import CSSSelector
from lxml.etree import _Element
from rapidfuzz import fuzz
from .utils import _match_record, _match_value_to_rule
from desktop_env.evaluators.metrics.utils import _match_record, _match_value_to_rule
logger = logging.getLogger("desktopenv.metric.general")
@@ -321,11 +321,11 @@ def check_direct_json_object(result, rules) -> float:
expected_json = rules["expected"]
for key in expected_json.keys():
if isinstance(expected_json.get(key), List):
if isinstance(expected_json.get(key), list):
flag = 0
expected_value_list = expected_json.get(key)
for each_expected_value in expected_value_list:
if each_expected_value in result.get(key):
if isinstance(result.get(key), list) and each_expected_value in result.get(key):
flag = 1
break
if flag == 0:
@@ -484,3 +484,15 @@ def compare_python_pure_text(py_file_path, gold_file_path):
return 1
else:
return 0
if __name__ == '__main__':
print(check_direct_json_object([], rules={
"relativeTime": {
"from": "5th next month"
},
"expected": {
"start": "SEA",
"end": "NYC",
"time": "{DoW}, {Month} {DayD}, {Year}",
"category": "Miles"
}}))

65
mm_agents/README.md Normal file
View File

@@ -0,0 +1,65 @@
# Agent
## Prompt-based Agents
### Supported Models
We currently support the following models as the foundation models for the agents:
- `GPT-3.5` (gpt-3.5-turbo-16k, ...)
- `GPT-4` (gpt-4-0125-preview, gpt-4-1106-preview, ...)
- `GPT-4V` (gpt-4-vision-preview, ...)
- `Gemini-Pro`
- `Gemini-Pro-Vision`
- `Claude-3, 2` (claude-3-haiku-2024030, claude-3-sonnet-2024022, ...)
- ...
And those from open-source community:
- `Mixtral 8x7B`
- `QWEN`, `QWEN-VL`
- `CogAgent`
- ...
And we will integrate and support more foundation models to support digital agent in the future, stay tuned.
### How to use
```python
from mm_agents.agent import PromptAgent
agent = PromptAgent(
model="gpt-4-0125-preview",
observation_type="screenshot",
)
agent.reset()
# say we have a instruction and observation
instruction = "Please help me to find the nearest restaurant."
obs = {"screenshot": "path/to/observation.jpg"}
response, actions = agent.predict(
instruction,
obs
)
```
### Observation Space and Action Space
We currently support the following observation spaces:
- `a11y_tree`: the a11y tree of the current screen
- `screenshot`: a screenshot of the current screen
- `screenshot_a11y_tree`: a screenshot of the current screen with a11y tree
- `som`: the set-of-mark trick on the current screen, with a table metadata
And the following action spaces:
- `pyautogui`: valid python code with `pyauotgui` code valid
- `computer_13`: a set of enumerated actions designed by us
To use feed an observation into the agent, you have to keep the obs variable as a dict with the corresponding information:
```python
obs = {
"screenshot": "path/to/observation.jpg",
"a11y_tree": "" # [a11y_tree data]
}
response, actions = agent.predict(
instruction,
obs
)
```
## Efficient Agents, Q* Agents, and more
Stay tuned for more updates.

View File

@@ -180,6 +180,7 @@ def trim_accessibility_tree(linearized_accessibility_tree, max_tokens):
linearized_accessibility_tree += "[...]\n"
return linearized_accessibility_tree
class PromptAgent:
def __init__(
self,
@@ -572,22 +573,10 @@ class PromptAgent:
logger.debug("CLAUDE MESSAGE: %s", repr(claude_messages))
# headers = {
# "x-api-key": os.environ["ANTHROPIC_API_KEY"],
# "anthropic-version": "2023-06-01",
# "content-type": "application/json"
# }
# headers = {
# "Accept": "application / json",
# "Authorization": "Bearer " + os.environ["ANTHROPIC_API_KEY"],
# "User-Agent": "Apifox/1.0.0 (https://apifox.com)",
# "Content-Type": "application/json"
# }
headers = {
"Authorization": os.environ["ANTHROPIC_API_KEY"],
"Content-Type": "application/json"
"x-api-key": os.environ["ANTHROPIC_API_KEY"],
"anthropic-version": "2023-06-01",
"content-type": "application/json"
}
payload = {
@@ -598,28 +587,21 @@ class PromptAgent:
"top_p": top_p
}
max_attempts = 20
attempt = 0
while attempt < max_attempts:
# response = requests.post("https://api.aigcbest.top/v1/chat/completions", headers=headers, json=payload)
response = requests.post("https://token.cluade-chat.top/v1/chat/completions", headers=headers,
json=payload)
if response.status_code == 200:
result = response.json()['choices'][0]['message']['content']
break
else:
logger.error(f"Failed to call LLM: {response.text}")
time.sleep(10)
attempt += 1
response = requests.post(
"https://api.anthropic.com/v1/messages",
headers=headers,
json=payload
)
if response.status_code != 200:
logger.error("Failed to call LLM: " + response.text)
time.sleep(5)
return ""
else:
print("Exceeded maximum attempts to call LLM.")
result = ""
return result
return response.json()['content'][0]['text']
elif self.model.startswith("mistral"):
print("Call mistral")
messages = payload["messages"]
max_tokens = payload["max_tokens"]
top_p = payload["top_p"]
@@ -652,7 +634,9 @@ class PromptAgent:
response = client.chat.completions.create(
messages=mistral_messages,
model=self.model,
max_tokens=max_tokens
max_tokens=max_tokens,
top_p=top_p,
temperature=temperature
)
break
except:
@@ -670,7 +654,6 @@ class PromptAgent:
elif self.model.startswith("THUDM"):
# THUDM/cogagent-chat-hf
print("Call CogAgent")
messages = payload["messages"]
max_tokens = payload["max_tokens"]
top_p = payload["top_p"]
@@ -703,7 +686,9 @@ class PromptAgent:
payload = {
"model": self.model,
"max_tokens": max_tokens,
"messages": cog_messages
"messages": cog_messages,
"temperature": temperature,
"top_p": top_p
}
base_url = "http://127.0.0.1:8000"
@@ -717,7 +702,6 @@ class PromptAgent:
print("Failed to call LLM: ", response.status_code)
return ""
elif self.model.startswith("gemini"):
def encoded_img_to_pil_img(data_str):
base64_str = data_str.replace("data:image/png;base64,", "")
@@ -802,7 +786,8 @@ class PromptAgent:
messages = payload["messages"]
max_tokens = payload["max_tokens"]
top_p = payload["top_p"]
temperature = payload["temperature"]
if payload["temperature"]:
logger.warning("Qwen model does not support temperature parameter, it will be ignored.")
qwen_messages = []
@@ -821,7 +806,9 @@ class PromptAgent:
response = dashscope.MultiModalConversation.call(
model='qwen-vl-plus',
messages=messages, # todo: add the hyperparameters
messages=messages,
max_length=max_tokens,
top_p=top_p,
)
# The response status_code is HTTPStatus.OK indicate success,
# otherwise indicate request is failed, you can get error code

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

View File

@@ -19,6 +19,28 @@ My computer's password is 'password', feel free to use it when you need sudo rig
First give the current screenshot and previous things we did a short reflection, then RETURN ME THE CODE OR SPECIAL CODE I ASKED FOR. NEVER EVER RETURN ME ANYTHING ELSE.
""".strip()
SYS_PROMPT_IN_SCREENSHOT_OUT_CODE_FEW_SHOT = """
You are an agent which follow my instruction and perform desktop computer tasks as instructed.
You have good knowledge of computer and good internet connection and assume your code will run on a computer for controlling the mouse and keyboard.
For each step, you will get an observation of an image, which is the screenshot of the computer screen and the instruction and you will predict the next action to operate on the computer based on the image.
You are required to use `pyautogui` to perform the action grounded to the observation, but DONOT use the `pyautogui.locateCenterOnScreen` function to locate the element you want to operate with since we have no image of the element you want to operate with. DONOT USE `pyautogui.screenshot()` to make screenshot.
Return one line or multiple lines of python code to perform the action each time, be time efficient. When predicting multiple lines of code, make some small sleep like `time.sleep(0.5);` interval so that the machine could take; Each time you need to predict a complete code, no variables or function can be shared from history
You need to to specify the coordinates of by yourself based on your observation of current observation, but you should be careful to ensure that the coordinates are correct.
You ONLY need to return the code inside a code block, like this:
```python
# your code here
```
Specially, it is also allowed to return the following special code:
When you think you have to wait for some time, return ```WAIT```;
When you think the task can not be done, return ```FAIL```, don't easily say ```FAIL```, try your best to do the task;
When you think the task is done, return ```DONE```.
My computer's password is 'password', feel free to use it when you need sudo rights.
Our past communication is great, and what you have done is very helpful. I will now give you another task to complete.
First take a deep breath, think step by step, give the current screenshot a thinking, then RETURN ME THE CODE OR SPECIAL CODE I ASKED FOR. NEVER EVER RETURN ME ANYTHING ELSE.
""".strip()
SYS_PROMPT_IN_SCREENSHOT_OUT_ACTION = """
You will act as an agent which follow my instruction and perform desktop computer tasks as instructed. You must have good knowledge of computer and good internet connection.
For each step, you will get an observation of an image, which is the screenshot of the computer screen. And you will predict the action of the computer based on the image.
@@ -264,6 +286,253 @@ You MUST choose and ONLY CHOOSE from the action space above, otherwise your acti
You CAN predict multiple actions at one step, but you should only return one action for each step.
""".strip()
SYS_PROMPT_IN_SCREENSHOT_OUT_ACTION_FEW_SHOT = """
You will act as an agent which follow my instruction and perform desktop computer tasks as instructed. You must have good knowledge of computer and good internet connection.
For each step, you will get an observation of an image, which is the screenshot of the computer screen and a task instruction. And you will predict the action of the computer based on the image.
HERE is the description of the action space you need to predict, follow the format and choose the correct action type and parameters:
ACTION_SPACE = [
{
"action_type": "MOVE_TO",
"note": "move the cursor to the specified position",
"parameters": {
"x": {
"type": float,
"range": [0, X_MAX],
"optional": False,
},
"y": {
"type": float,
"range": [0, Y_MAX],
"optional": False,
}
}
},
{
"action_type": "CLICK",
"note": "click the left button if the button not specified, otherwise click the specified button; click at the current position if x and y are not specified, otherwise click at the specified position",
"parameters": {
"button": {
"type": str,
"range": ["left", "right", "middle"],
"optional": True,
},
"x": {
"type": float,
"range": [0, X_MAX],
"optional": True,
},
"y": {
"type": float,
"range": [0, Y_MAX],
"optional": True,
},
"num_clicks": {
"type": int,
"range": [1, 2, 3],
"optional": True,
},
}
},
{
"action_type": "MOUSE_DOWN",
"note": "press the left button if the button not specified, otherwise press the specified button",
"parameters": {
"button": {
"type": str,
"range": ["left", "right", "middle"],
"optional": True,
}
}
},
{
"action_type": "MOUSE_UP",
"note": "release the left button if the button not specified, otherwise release the specified button",
"parameters": {
"button": {
"type": str,
"range": ["left", "right", "middle"],
"optional": True,
}
}
},
{
"action_type": "RIGHT_CLICK",
"note": "right click at the current position if x and y are not specified, otherwise right click at the specified position",
"parameters": {
"x": {
"type": float,
"range": [0, X_MAX],
"optional": True,
},
"y": {
"type": float,
"range": [0, Y_MAX],
"optional": True,
}
}
},
{
"action_type": "DOUBLE_CLICK",
"note": "double click at the current position if x and y are not specified, otherwise double click at the specified position",
"parameters": {
"x": {
"type": float,
"range": [0, X_MAX],
"optional": True,
},
"y": {
"type": float,
"range": [0, Y_MAX],
"optional": True,
}
}
},
{
"action_type": "DRAG_TO",
"note": "drag the cursor to the specified position with the left button pressed",
"parameters": {
"x": {
"type": float,
"range": [0, X_MAX],
"optional": False,
},
"y": {
"type": float,
"range": [0, Y_MAX],
"optional": False,
}
}
},
{
"action_type": "SCROLL",
"note": "scroll the mouse wheel up or down",
"parameters": {
"dx": {
"type": int,
"range": None,
"optional": False,
},
"dy": {
"type": int,
"range": None,
"optional": False,
}
}
},
{
"action_type": "TYPING",
"note": "type the specified text",
"parameters": {
"text": {
"type": str,
"range": None,
"optional": False,
}
}
},
{
"action_type": "PRESS",
"note": "press the specified key and release it",
"parameters": {
"key": {
"type": str,
"range": KEYBOARD_KEYS,
"optional": False,
}
}
},
{
"action_type": "KEY_DOWN",
"note": "press the specified key",
"parameters": {
"key": {
"type": str,
"range": KEYBOARD_KEYS,
"optional": False,
}
}
},
{
"action_type": "KEY_UP",
"note": "release the specified key",
"parameters": {
"key": {
"type": str,
"range": KEYBOARD_KEYS,
"optional": False,
}
}
},
{
"action_type": "HOTKEY",
"note": "press the specified key combination",
"parameters": {
"keys": {
"type": list,
"range": [KEYBOARD_KEYS],
"optional": False,
}
}
},
############################################################################################################
{
"action_type": "WAIT",
"note": "wait until the next action",
},
{
"action_type": "FAIL",
"note": "decide the task can not be performed",
},
{
"action_type": "DONE",
"note": "decide the task is done",
}
]
Firstly you need to predict the class of your action, then you need to predict the parameters of your action:
- For MOUSE_MOVE, you need to predict the x and y coordinate of the mouse cursor, the left top corner of the screen is (0, 0), the right bottom corner of the screen is (1920, 1080)
for example, format as:
```
{
"action_type": "MOUSE_MOVE",
"x": 1319.11,
"y": 65.06
}
```
- For [CLICK, MOUSE_DOWN, MOUSE_UP], you need to specify the click_type as well, select from [LEFT, MIDDLE, RIGHT, WHEEL_UP, WHEEL_DOWN], which means you click the left button, middle button, right button, wheel up or wheel down of your mouse:
for example, format as:
```
{
"action_type": "CLICK",
"click_type": "LEFT"
}
```
- For [KEY, KEY_DOWN, KEY_UP], you need to choose a(multiple) key(s) from the keyboard
for example, format as:
```
{
"action_type": "KEY",
"key": "ctrl+c"
}
```
- For TYPE, you need to specify the text you want to type
for example, format as:
```
{
"action_type": "TYPE",
"text": "hello world"
}
```
REMEMBER:
For every step, you should only RETURN ME THE action_type AND parameters I ASKED FOR. NEVER EVER RETURN ME ANYTHING ELSE.
You MUST wrap the dict with backticks (\`).
You MUST choose and ONLY CHOOSE from the action space above, otherwise your action will be considered as invalid and you will get a penalty.
You CAN predict multiple actions at one step, but you should only return one action for each step.
Our past communication is great, and what you have done is very helpful. I will now give you another task to complete.
""".strip()
SYS_PROMPT_IN_A11Y_OUT_CODE = """
You are an agent which follow my instruction and perform desktop computer tasks as instructed.
You have good knowledge of computer and good internet connection and assume your code will run on a computer for controlling the mouse and keyboard.

View File

@@ -50,3 +50,4 @@ pypdf2
pdfplumber
wandb
wrapt_timeout_decorator
gdown

View File

@@ -1,3 +0,0 @@
# Resource Collection
Manually gain some insights, then scale with careful code.

View File

@@ -1,28 +0,0 @@
import praw
def search_reddit(keyword, subreddit, client_id, client_secret, user_agent, limit=10000):
# Initialize PRAW with your Reddit application credentials
reddit = praw.Reddit(client_id=client_id,
client_secret=client_secret,
user_agent=user_agent)
# Search the specified subreddit for the keyword
results = reddit.subreddit(subreddit).search(keyword, limit=limit)
for post in results:
print(f"Title: {post.title}")
print(f"URL: {post.url}")
print(f"Score: {post.score}")
print(f"Comments: {post.num_comments}")
print("------------------------")
# Example usage
if __name__ == "__main__":
CLIENT_ID = 'YOUR_CLIENT_ID'
CLIENT_SECRET = 'YOUR_CLIENT_SECRET'
USER_AGENT = 'my_reddit_scraper'
keyword = "how to"
subreddit = "vscode"
search_reddit(keyword, subreddit, CLIENT_ID, CLIENT_SECRET, USER_AGENT)

File diff suppressed because it is too large Load Diff

View File

@@ -1,268 +0,0 @@
id,Source,InvolvedApp
94d95f96-9699-4208-98ba-3c3119edf9c2,https://help.ubuntu.com/lts/ubuntu-help/addremove-install.html.en,OS
bedcedc4-4d72-425e-ad62-21960b11fe0d,https://www.youtube.com/watch?v=D4WyNjt_hbQ&t=2s,OS
43c2d64c-bab5-4dcb-a30c-b888321c319a,https://ubuntu.com/tutorials/command-line-for-beginners#4-creating-folders-and-files,OS
7688b85f-87a4-4e4a-b2f8-f3d6c3f29b82,https://ubuntu.com/tutorials/command-line-for-beginners#5-moving-and-manipulating-files,OS
ec4e3f68-9ea4-4c18-a5c9-69f89d1178b3,https://www.youtube.com/watch?v=D4WyNjt_hbQ&t=2s,OS
a462a795-fdc7-4b23-b689-e8b6df786b78,https://help.ubuntu.com/lts/ubuntu-help/shell-exit.html.en,OS
f9be0997-4b7c-45c5-b05c-4612b44a6118,https://help.ubuntu.com/lts/ubuntu-help/shell-notifications.html.en,OS
ae039631-2b12-4637-84f6-c67d51511be3,https://help.ubuntu.com/lts/ubuntu-help/net-default-browser.html.en,OS
e2eb4bf1-aa93-4192-b55d-03e2fb6dfd15,https://help.ubuntu.com/lts/ubuntu-help/contacts-add-remove.html.en,OS
28cc3b7e-b194-4bc9-8353-d04c0f4d56d2,https://help.ubuntu.com/lts/ubuntu-help/sound-volume.html.en,OS
5ea617a3-0e86-4ba6-aab2-dac9aa2e8d57,https://help.ubuntu.com/lts/ubuntu-help/files-recover.html.en,OS
e0df059f-28a6-4169-924f-b9623e7184cc,https://help.ubuntu.com/lts/ubuntu-help/files-rename.html.en,OS
ddc75b62-7311-4af8-bfb3-859558542b36,https://help.ubuntu.com/lts/ubuntu-help/addremove-remove.html.en,OS
5c433d22-ed9a-4e31-91f5-54cf3e8acd63,https://help.ubuntu.com/lts/ubuntu-help/session-language.html.zh-CN,OS
b6781586-6346-41cd-935a-a6b1487918fc,https://help.ubuntu.com/lts/ubuntu-help/clock-timezone.html.en,OS
b3d4a89c-53f2-4d6b-8b6a-541fb5d205fa,https://help.ubuntu.com/lts/ubuntu-help/bluetooth-turn-on-off.html.en,OS
3ce045a0-877b-42aa-8d2c-b4a863336ab8,https://help.ubuntu.com/lts/ubuntu-help/a11y-font-size.html.en,OS
fe41f596-a71b-4c2f-9b2f-9dcd40b568c3,https://help.ubuntu.com/lts/ubuntu-help/power-percentage.html.en,OS
a4d98375-215b-4a4d-aee9-3d4370fccc41,https://help.ubuntu.com/lts/ubuntu-help/privacy-screen-lock.html.en,OS
765d2b74-88a7-4d50-bf51-34e4106fd24a,https://help.ubuntu.com/lts/ubuntu-help/files-delete.html.en,OS
cc9d4f34-1ca0-4a1b-8ff2-09302696acb9,https://superuser.com/questions/178587/how-do-i-detach-a-process-from-terminal-entirely,OS
5812b315-e7bd-4265-b51f-863c02174c28,https://superuser.com/questions/149404/create-an-ssh-user-who-only-has-permission-to-access-specific-folders,OS
c56de254-a3ec-414e-81a6-83d2ce8c41fa,https://superuser.com/questions/28426/how-to-extract-text-with-ocr-from-a-pdf-on-linux,OS
6ebbfb01-ea72-4226-a2a6-dc428e111ed2,https://superuser.com/questions/46748/how-do-i-make-bash-my-default-shell-on-ubuntu,OS
4d2b519e-e872-4100-8ea3-fe71ab0f9133,https://stackoverflow.com/questions/11530090/adding-a-new-entry-to-the-path-variable-in-zsh,OS
c288e301-e626-4b98-a1ab-159dcb162af5,https://stackoverflow.com/questions/41986507/unable-to-set-default-python-version-to-python3-in-ubuntu,OS
13584542-872b-42d8-b299-866967b5c3ef,https://superuser.com/questions/72176/linux-set-default-terminal-size-and-screen-position,OS
23393935-50c7-4a86-aeea-2b78fd089c5c,https://superuser.com/questions/91307/copying-only-jpg-from-a-directory-structure-to-another-location-linux,OS
f10b16e1-c160-4cb3-989f-7b2ec89bc073,https://www.wikihow.com/Install-Gnome-on-Ubuntu,OS
eb03d19a-b88d-4de4-8a64-ca0ac66f426b,https://www.youtube.com/shorts/t9JLUaT55UQ,MS Excel
0bf05a7d-b28b-44d2-955a-50b41e24012a,https://www.youtube.com/shorts/FPAQaDTS8VY,MS Excel
7b802dad-6e0f-4204-9815-d4e3f57627d8,https://www.youtube.com/shorts/Of-lzeP1usE,MS Excel
7a4e4bc8-922c-4c84-865c-25ba34136be1,https://www.youtube.com/shorts/bvUhr1AHs44,MS Excel
2bd59342-0664-4ccb-ba87-79379096cc08,https://www.youtube.com/shorts/L3Z-F1QTQFY,MS Excel
a9f325aa-8c05-4e4f-8341-9e4358565f4f,https://www.youtube.com/shorts/A0gmEBRKXWs,MS Excel
ecb0df7a-4e8d-4a03-b162-053391d3afaf,https://www.youtube.com/shorts/tXOovKn0H68,MS Excel
7efeb4b1-3d19-4762-b163-63328d66303b,https://www.youtube.com/shorts/4jzXfZNhfmk,MS Excel
4e6fcf72-daf3-439f-a232-c434ce416af6,https://www.youtube.com/shorts/0uxJccNCKcE,MS Excel
6054afcb-5bab-4702-90a0-b259b5d3217c,https://www.youtube.com/shorts/JTbZ8sRxkdU,MS Excel
abed40dc-063f-4598-8ba5-9fe749c0615d,https://www.youtube.com/shorts/xgf4ZpsEx5M,MS Excel
01b269ae-2111-4a07-81fd-3fcd711993b0,https://www.youtube.com/shorts/VrUzPTIwQ04,MS Excel
8b1ce5f2-59d2-4dcc-b0b0-666a714b9a14,https://www.youtube.com/shorts/Hbcwu6IQ1ns,MS Excel
af2b02f7-acee-4be4-8b66-499fab394915,https://www.youtube.com/shorts/AwKsb5VmtBI,MS Excel
da1d63b8-fa12-417b-ba18-f748e5f770f3,https://www.youtube.com/shorts/hquscnbz2-U,MS Excel
636380ea-d5f6-4474-b6ca-b2ed578a20f1,https://www.youtube.com/shorts/_BYL6VOHLGw,"MS Excel, Edge"
5ba77536-05c5-4aae-a9ff-6e298d094c3e,https://www.youtube.com/shorts/CuBC1evUS5I,MS Excel
4bc4eaf4-ca5e-4db2-8138-8d4e65af7c0b,https://www.youtube.com/shorts/1adQWfjN-tI,MS Excel
672a1b02-c62f-4ae2-acf0-37f5fb3052b0,https://www.youtube.com/shorts/2rhdQXI4Lng,MS Excel
648fe544-16ba-44af-a587-12ccbe280ea6,https://www.youtube.com/shorts/sOPBMWaC6Uc,MS Excel
8985d1e4-5b99-4711-add4-88949ebb2308,https://www.youtube.com/shorts/J5ts2Acv9Pc,MS Excel
9e606842-2e27-43bf-b1d1-b43289c9589b,https://www.youtube.com/shorts/B-mGYDFOyUs,MS Excel
fcb6e45b-25c4-4087-9483-03d714f473a9,https://www.youtube.com/shorts/GZipp7nOZS0,MS Excel
68c0c5b7-96f3-4e87-92a7-6c1b967fd2d2,https://www.youtube.com/shorts/JEH5TsK-cCk,"MS Excel, Edge"
fff629ea-046e-4793-8eec-1a5a15c3eb35,https://www.youtube.com/shorts/8WybtCdUT6w,MS Excel
5c9a206c-bb00-4fb6-bb46-ee675c187df5,https://www.youtube.com/shorts/VbQtMNnq9i4,MS Excel
e975ae74-79bd-4672-8d1c-dc841a85781d,https://www.youtube.com/shorts/GjT7gGe5Sr8,MS Excel
34a6938a-58da-4897-8639-9b90d6db5391,https://www.youtube.com/shorts/gW37x2TkzOY,MS Excel
b5a22759-b4eb-4bf2-aeed-ad14e8615f19,https://www.youtube.com/shorts/3xLa-D0C7Ic,MS Excel
2f9913a1-51ed-4db6-bfe0-7e1c95b3139e,https://www.youtube.com/shorts/dGLRcmfVO6Q,MS Excel
2558031e-401d-4579-8e00-3ecf540fb492,https://www.mrexcel.com/board/threads/sales-for-the-first-6-weeks.1249213/,MS Excel
39aa4e37-dc91-482e-99af-132a612d40f3,https://www.libreofficehelp.com/add-insert-delete-copy-move-rename-a-worksheet-in-libreoffice-calc/,LibreOffice Calc
0cecd4f3-74de-457b-ba94-29ad6b5dafb6,https://www.libreofficehelp.com/add-insert-delete-copy-move-rename-a-worksheet-in-libreoffice-calc/,LibreOffice Calc
4188d3a4-077d-46b7-9c86-23e1a036f6c1,https://www.libreofficehelp.com/freeze-unfreeze-rows-columns-ranges-calc/,LibreOffice Calc
51b11269-2ca8-4b2a-9163-f21758420e78,https://www.reddit.com/r/LibreOfficeCalc/comments/186pcc6/how_to_arrange_numbers_in_a_column_from_minimum/,LibreOffice Calc
7e429b8d-a3f0-4ed0-9b58-08957d00b127,https://medium.com/@divyangichaudhari17/how-to-use-vlookup-and-hlookup-in-libre-calc-3370698bb3ff,LibreOffice Calc
f5a90742-3fa2-40fc-a564-f29b054e0337,https://superuser.com/questions/1236149/libreoffice-calc-how-to-apply-functions-to-columns,LibreOffice Calc
22df9241-f8d7-4509-b7f1-37e501a823f7,https://superuser.com/questions/1767185/how-do-you-move-cells-in-libreoffice-calc,LibreOffice Calc
1434ca3e-f9e3-4db8-9ca7-b4c653be7d17,https://www.wikihow.com/Remove-Duplicates-in-Open-Office-Calc,LibreOffice Calc
347ef137-7eeb-4c80-a3bb-0951f26a8aff,https://www.youtube.com/watch?v=bgO40-CjYNY,LibreOffice Calc
6e99a1ad-07d2-4b66-a1ce-ece6d99c20a5,https://www.youtube.com/watch?v=nl-nXjJurhQ,LibreOffice Calc
3aaa4e37-dc91-482e-99af-132a612d40f3,https://www.quora.com/How-can-you-import-export-CSV-files-with-LibreOffice-Calc-or-OpenOffice,LibreOffice Calc
0decd4f3-74de-457b-ba94-29ad6b5dafb6,https://justclickhere.co.uk/resources/checkboxes-tick-boxes-libreoffice-calc/,LibreOffice Calc
37608790-6147-45d0-9f20-1137bb35703d,https://www.youtube.com/shorts/uzPo_CPCHH8,MS Excel
f9584479-3d0d-4c79-affa-9ad7afdd8850,https://youtube.com/shorts/feldd-Pn48c?si=9xJiem2uAHm6Jshb,LibreOffice Calc
d681960f-7bc3-4286-9913-a8812ba3261a,https://www.youtube.com/shorts/d7U1S_IsTVM,LibreOffice Calc
f6a90742-3fa2-40fc-a564-f29b054e0337,https://www.excel-easy.com/examples/drop-down-list.html,LibreOffice Calc
21df9241-f8d7-4509-b7f1-37e501a823f7,https://www.youtube.com/watch?v=p5C4V_AO1UU,LibreOffice Calc
1334ca3e-f9e3-4db8-9ca7-b4c653be7d17,https://techcommunity.microsoft.com/t5/excel/excel-workbook-top-way-too-big-can-t-see-rows-and-columns/m-p/4014694,LibreOffice Calc
357ef137-7eeb-4c80-a3bb-0951f26a8aff,https://www.reddit.com/r/excel/comments/17zny8u/calculating_total_amount_earned_from_total_hours/,LibreOffice Calc
6f99a1ad-07d2-4b66-a1ce-ece6d99c20a5,https://techcommunity.microsoft.com/t5/excel/sumarize-the-sheetnames/m-p/4014716,LibreOffice Calc
aa3a8974-2e85-438b-b29e-a64df44deb4b,https://www.quora.com/Libre-Office-Calc-How-do-I-resize-all-cells-in-a-sheet-to-make-them-fit-to-1-page-for-printing-and-exporting-as-PDF,LibreOffice Calc
a01fbce3-2793-461f-ab86-43680ccbae25,https://superuser.com/questions/1250677/how-to-set-decimal-separator-in-libre-office-calc,LibreOffice Calc
4f07fbe9-70de-4927-a4d5-bb28bc12c52c,https://superuser.com/questions/1081048/libreoffice-calc-how-to-pad-number-to-fixed-decimals-when-used-within-formula,LibreOffice Calc
e3b1d5fa-ed00-4129-bda1-1452bd2b6772,https://www.reddit.com/r/libreoffice/comments/tel112/calc_how_to_calculate_sum_by_categories/,LibreOffice Calc
ca6a9524-f8e9-4d2f-9364-ab0cad567739,https://www.reddit.com/r/libreoffice/comments/113gmyc/how_to_remove_certain_text_from_cells_in_calc/,LibreOffice Calc
a455e8d0-930f-40d2-9575-5e8d2d222f58,https://superuser.com/questions/562944/quickly-fill-blank-cells-in-a-list-in-libreoffice-calc,LibreOffice Calc
83ee22c6-7737-49ce-9b5a-138c3e92af04,https://superuser.com/questions/661102/currency-conversion-in-libreoffice-calc,LibreOffice Calc
819f61c2-ec77-4d3f-9996-0838ae5aacc8,https://superuser.com/questions/381696/creating-a-column-of-working-days-in-libreoffice-calc,LibreOffice Calc
69d577b3-004e-4bca-89b2-0d7c2f6049e3,https://superuser.com/questions/387106/libreoffice-calc-how-to-get-total-for-hhmmss-cells,LibreOffice Calc
0a1bf4ca-d4ea-4618-baa5-6e8dc1b46d82,https://superuser.com/questions/571915/sum-up-to-n-highest-value-out-of-a-series,LibreOffice Calc
ac9bb6cb-1888-43ab-81e4-a98a547918cd,https://superuser.com/questions/1674211/how-to-change-colour-of-slide-number-in-libre-office,LibreOffice Impress
5d901039-a89c-4bfb-967b-bf66f4df075e,https://superuser.com/questions/986776/how-can-i-stretch-an-image-in-a-libreoffice-impress-presentation-to-fill-the-pag,LibreOffice Impress
071d4ace-091a-4ec3-886e-f4be55ae375d,https://superuser.com/questions/706860/hide-slide-numbers-and-slide-footer-on-first-and-second-slide-in-libreoffice-imp?rq=1,LibreOffice Impress
550ce7e7-747b-495f-b122-acdc4d0b8e54,"https://technical-tips.com/blog/software/text-in-libreoffice-strikethrough--6948#:~:text=To%20strikethrough%20Text%20in%20LibreOffice%201%20In%20your,effect%22%20can%20your%20additionally%2C%20for%20example%2C%20double%20underline.",LibreOffice Impress
455d3c66-7dc6-4537-a39a-36d3e9119df7,"https://www.libreofficehelp.com/export-libreoffice-impress-slides-images/#:~:text=Exporting%20a%20single%20slide%20as.jpg%2C.png%2C%20etc%20image%20is,on%20the%20checkbox%20Selection.%20Provide%20jpg%20quality%20options.",LibreOffice Impress
af23762e-2bfd-4a1d-aada-20fa8de9ce07,https://superuser.com/questions/1059080/how-to-make-a-summary-slide-in-impress-listing-the-titles-of-all-slides-autom,LibreOffice Impress
c59742c0-4323-4b9d-8a02-723c251deaa0,https://www.reddit.com/r/libreoffice/comments/17lcdrp/audio_not_supported_in_libreoffice_impress/,LibreOffice Impress
39478d4a-1049-456f-aa77-407811393add,https://www.reddit.com/r/libreoffice/comments/jul3o8/putting_cap_or_hat_or_carat_symbol_in_libre/,LibreOffice Impress
c3ad4442-499f-4e58-bc4e-1a1417ea9b8c,http://maharajacollege.ac.in/material/Libreofficeimpresspdf.pdf,LibreOffice Impress
ef9d12bd-bcee-4ba0-a40e-918400f43ddf,https://www.reddit.com/r/libreoffice/comments/18elh3y/i_closed_the_slide_pannel_on_the_left_and_idk_how/,LibreOffice Impress
9ec204e4-f0a3-42f8-8458-b772a6797cab,https://www.tiktok.com/@lil.d1rt_/video/7247574148887629083,LibreOffice Impress
0f84bef9-9790-432e-92b7-eece357603fb,https://stackoverflow.com/questions/29036788/how-to-disable-libreoffice-impress-to-use-multiple-display,LibreOffice Impress
ce88f674-ab7a-43da-9201-468d38539e4a,https://justclickhere.co.uk/resources/change-slides-in-impress-to-portrait/,LibreOffice Impress
f0a334af-f91b-4c03-b578-aac9bec2b543,https://www.libreofficehelp.com/insert-video-impress-presentation/#Inserting_a_Video_in_Impress,LibreOffice Impress
3b27600c-3668-4abd-8f84-7bcdebbccbdb,https://www.libreofficehelp.com/change-slide-background-impress/#All_Slides,LibreOffice Impress
a097acff-6266-4291-9fbd-137af7ecd439,https://www.youtube.com/watch?v=DDmEvjs4iBw,LibreOffice Impress
21760ecb-8f62-40d2-8d85-0cee5725cb72,https://www.libreofficehelp.com/add-animations-transitions-libreoffice-impress-slides/,LibreOffice Impress
3cc4f35d-fa2e-4555-afb9-741b7c062a74,https://documentation.libreoffice.org/assets/Uploads/Documentation/en/IG7.6/IG76-ImpressGuide.pdf,LibreOffice Impress
6ada715d-3aae-4a32-a6a7-429b2e43fb93,https://www.quora.com/How-do-you-insert-images-into-a-LibreOffice-Writer-document,LibreOffice Writer
ecc2413d-8a48-416e-a3a2-d30106ca36cb,https://www.quora.com/How-can-I-insert-a-blank-page-on-libreoffice,LibreOffice Writer
0e47de2a-32e0-456c-a366-8c607ef7a9d2,https://ask.libreoffice.org/t/how-to-start-page-numbering-on-a-certain-page/39931/4,LibreOffice Writer
4bcb1253-a636-4df4-8cb0-a35c04dfef31,https://www.libreofficehelp.com/save-export-writer-documents-in-pdf-epub-format/,LibreOffice Writer
0810415c-bde4-4443-9047-d5f70165a697,https://www.youtube.com/watch?v=Q_AaL6ljudU,LibreOffice Writer
e528b65e-1107-4b8c-8988-490e4fece599,https://www.youtube.com/watch?v=l25Evu4ohKg,LibreOffice Writer
66399b0d-8fda-4618-95c4-bfc6191617e9,https://www.youtube.com/watch?v=l25Evu4ohKg,LibreOffice Writer
936321ce-5236-426a-9a20-e0e3c5dc536f,https://www.youtube.com/watch?v=l25Evu4ohKg,LibreOffice Writer
663876c7-3471-43db-ba51-f410b13d9d7d,https://askubuntu.com/questions/319593/how-to-type-science-equations-in-libre-office,LibreOffice Writer
3ef2b351-8a84-4ff2-8724-d86eae9b842e,https://askubuntu.com/questions/1066351/how-do-you-center-align-in-libreoffice#:~:text=Ctrl%20%2B%20e%20will%20Center%20align%20the%20cursor%20for%20you.,LibreOffice Writer
45d61a06-6545-4422-97b7-bc76cfa964c1,https://stackoverflow.com/questions/71685737/how-to-replace-all-newlines-with-paragraph-marks-in-libreoffice-write,LibreOffice Writer
0b17a146-2934-46c7-8727-73ff6b6483e8,https://askubuntu.com/questions/245695/how-do-you-insert-subscripts-and-superscripts-into-ordinary-non-formula-text-i,LibreOffice Writer
0e763496-b6bb-4508-a427-fad0b6c3e195,https://ask.libreoffice.org/t/how-do-i-change-the-font-for-the-whole-document-in-writer/9220,LibreOffice Writer
f178a4a9-d090-4b56-bc4c-4b72a61a035d,https://ask.libreoffice.org/t/how-do-i-make-times-new-roman-the-default-font-in-lo/64604,LibreOffice Writer
0a0faba3-5580-44df-965d-f562a99b291c,https://stackoverflow.com/questions/64528055/how-to-make-part-of-my-sentence-left-aligned-and-rest-as-right-aligned,LibreOffice Writer
e246f6d8-78d7-44ac-b668-fcf47946cb50,https://ask.libreoffice.org/t/how-to-change-text-size-color-of-italic-font/77712,LibreOffice Writer
8472fece-c7dd-4241-8d65-9b3cd1a0b568,https://stackoverflow.com/questions/37259827/libreoffice-writer-how-to-set-different-colors-to-each-letter,LibreOffice Writer
88fe4b2d-3040-4c70-9a70-546a47764b48,https://stackoverflow.com/questions/56554555/libreoffice-writer-how-to-create-empty-line-space-after-every-period-in-a-par,LibreOffice Writer
6a33f9b9-0a56-4844-9c3f-96ec3ffb3ba2,https://superuser.com/questions/762500/how-do-i-find-all-highlighted-text-in-libreoffice-writer,LibreOffice Writer
d53ff5ee-3b1a-431e-b2be-30ed2673079b,https://ask.libreoffice.org/t/how-to-convert-all-uppercase-to-lowercase/53341,LibreOffice Writer
72b810ef-4156-4d09-8f08-a0cf57e7cefe,https://superuser.com/questions/657792/libreoffice-writer-how-to-apply-strikethrough-text-formatting?rq=1,LibreOffice Writer
6f81754e-285d-4ce0-b59e-af7edb02d108,https://superuser.com/questions/789473/remove-duplicate-lines-in-libreoffice-openoffice-writer,LibreOffice Writer
41c621f7-3544-49e1-af8d-dafd0f834f75,https://superuser.com/questions/1668018/how-to-auto-format-lines-in-libre-office-writer,LibreOffice Writer
b21acd93-60fd-4127-8a43-2f5178f4a830,https://superuser.com/questions/1097199/how-can-i-double-space-a-document-in-libreoffice?rq=1,LibreOffice Writer
59f21cfb-0120-4326-b255-a5b827b38967,https://docs.videolan.me/vlc-user/desktop/3.0/en/basic/media.html#playing-a-file,VLC player
8ba5ae7a-5ae5-4eab-9fcc-5dd4fe3abf89,https://docs.videolan.me/vlc-user/desktop/3.0/en/basic/recording/playing.html#choose-your-recordings-folder,VLC player
8f080098-ddb1-424c-b438-4e96e5e4786e,https://medium.com/@jetscribe_ai/how-to-extract-mp3-audio-from-videos-using-vlc-media-player-beeef644ebfb,VLC player
bba3381f-b5eb-4439-bd9e-80c22218d5a7,https://www.quora.com/How-do-I-play-online-videos-using-the-VLC-media-player,VLC player
a1c3ab35-02de-4999-a7ed-2fd12c972c6e,https://www.quora.com/How-do-I-compress-a-video-with-VLC,VLC player
fba2c100-79e8-42df-ae74-b592418d54f4,https://www.youtube.com/watch?v=XHprwDJ0-fU&t=436s,VLC player
d70666e4-7348-42c7-a06a-664094c5df3c,https://www.youtube.com/watch?v=XHprwDJ0-fU&t=436s,VLC player
efcf0d81-0835-4880-b2fd-d866e8bc2294,"https://www.youtube.com/watch?v=XHprwDJ0-fU&t=436s, https://help.ubuntu.com/stable/ubuntu-help/look-background.html.en",VLC player
8d9fd4e2-6fdb-46b0-b9b9-02f06495c62f,https://www.youtube.com/watch?v=XHprwDJ0-fU&t=436s,VLC player
aa4b5023-aef6-4ed9-bdc9-705f59ab9ad6,https://videoconverter.wondershare.com/vlc/how-to-rotate-a-video-using-vlc.html?gad_source=1&gclid=CjwKCAiA-vOsBhAAEiwAIWR0TaGSOLkYiBeVQGZSyfeUP3g-tIvYxffl5RFIu0-zrUL1IF41eCw1JRoCnCMQAvD_BwE,VLC player
386dbd0e-0241-4a0a-b6a2-6704fba26b1c,https://superuser.com/questions/1708415/pause-and-play-vlc-in-background?rq=1,VLC player
9195653c-f4aa-453d-aa95-787f6ccfaae9,https://superuser.com/questions/1513285/how-can-i-increase-the-maximum-volume-output-by-vlc?rq=1,VLC player
5ac2891a-eacd-4954-b339-98abba077adb,"https://superuser.com/questions/1412810/how-to-prevent-vlc-media-player-from-auto-closing-after-video-end#:%7E:text=Click%20on%20%22Media%22on%20the,VLC%20player%20after%20video%20ending",VLC player
0d95d28a-9587-433b-a805-1fbe5467d598,https://superuser.com/questions/1299036/vlc-how-to-open-the-folder-of-the-current-playing-video?noredirect=1&lq=1,VLC player
d06f0d4d-2cd5-4ede-8de9-598629438c6e,https://superuser.com/questions/1039392/changing-colour-of-vlc-volume-slider,VLC player
a5bbbcd5-b398-4c91-83d4-55e1e31bbb81,https://superuser.com/questions/776056/how-to-hide-bottom-toolbar-in-vlc,VLC player
f3977615-2b45-4ac5-8bba-80c17dbe2a37,https://www.reddit.com/r/Fedora/comments/rhljzd/how_to_run_multiple_instances_of_vlc_media_player/,VLC player
c669a35f-d45a-450e-b1f2-f473748337bb,https://www.quora.com/How-do-I-fast-forward-a-video-in-VLC-player,VLC player
d1ba14d0-fef8-4026-8418-5b581dc68ca0,https://superuser.com/questions/306154/how-to-use-a-b-repeat-feature-of-vlc,VLC player
215dfd39-f493-4bc3-a027-8a97d72c61bf,https://superuser.com/questions/1224784/how-to-change-vlcs-splash-screen,VLC player
bb5e4c0d-f964-439c-97b6-bdb9747de3f4,https://www.wikihow.com/Remove-an-Email-Account-from-Thunderbird,ThunderBird
7b6c7e24-c58a-49fc-a5bb-d57b80e5b4c3,https://www.wikihow.com/Access-Gmail-With-Mozilla-Thunderbird,ThunderBird
b188fe10-ae67-4db8-a154-26a0b8ff8f1e,https://www.reddit.com/r/Thunderbird/comments/17vv2os/restore_readability_in_message_list_pane/,ThunderBird
12086550-11c0-466b-b367-1d9e75b3910e,https://www.bitrecover.com/blog/manage-thunderbird-profiles/,ThunderBird
06fe7178-4491-4589-810f-2e2bc9502122,https://www.quora.com/How-do-I-backup-email-files-in-Mozilla-Thunderbird,ThunderBird
6766f2b8-8a72-417f-a9e5-56fcaa735837,"https://www.adsigner.com/user-manual/signatures/setup-email-client-thunderbird/#:~:text=is%20probably%20hidden.-,Right%20click%20on%20the%20empty%20space%20at%20the%20top%20of,signature%20from%20a%20file%20instead.",ThunderBird
e1e75309-3ddb-4d09-92ec-de869c928143,https://support.mozilla.org/en-US/kb/organize-your-messages-using-filters,ThunderBird
3d1682a7-0fb0-49ae-a4dc-a73afd2d06d5,https://support.mozilla.org/en-US/kb/organize-your-messages-using-filters,ThunderBird
35253b65-1c19-4304-8aa4-6884b8218fc0,https://support.mozilla.org/en-US/questions/1259354,ThunderBird
d088f539-cab4-4f9a-ac92-9999fc3a656e,https://support.mozilla.org/en-US/kb/how-use-attachments,ThunderBird
2ad9387a-65d8-4e33-ad5b-7580065a27ca,"https://support.mozilla.org/bm/questions/1027435, https://www.wikihow.tech/Create-Folders-in-Mozilla-Thunderbird",ThunderBird
480bcfea-d68f-4aaa-a0a9-2589ef319381,https://www.reddit.com/r/Thunderbird/comments/182dg5p/unified_inbox_howto/,ThunderBird
37b9808f-b2b4-4177-ab00-9ddfae4bad27,https://www.quora.com/How-can-I-schedule-Mozilla-Thunderbird-to-turn-off-automatically,ThunderBird
af630914-714e-4a24-a7bb-f9af687d3b91,https://stackoverflow.com/questions/11333148/adding-a-toolbar-button-to-a-thundebird-compose-message-window?rq=3,ThunderBird
3299584d-8f11-4457-bf4c-ce98f7600250,https://superuser.com/questions/1643561/would-like-to-see-the-email-address-from-sender-in-the-column,ThunderBird
030eeff7-b492-4218-b312-701ec99ee0cc,https://superuser.com/questions/1781004/how-do-i-remove-the-indentation-and-character-in-quoted-text-of-a-reply-mess,ThunderBird
94760984-3ff5-41ee-8347-cf1af709fea0,https://superuser.com/questions/1757333/how-can-i-view-thunderbird-in-full-dark-mode,ThunderBird
99146c54-4f37-4ab8-9327-5f3291665e1e,https://superuser.com/questions/1764409/how-to-send-email-with-thunderbird-without-configuring-an-incoming-email-service,ThunderBird
9656a811-9b5b-4ddf-99c7-5117bcef0626,https://superuser.com/questions/205240/is-there-a-way-to-get-a-popup-confirmation-box-when-you-send-an-email-in-thunder?rq=1,ThunderBird
c9e7eaf2-b1a1-4efc-a982-721972fa9f02,https://superuser.com/questions/544480/how-to-apply-automatic-message-filters-to-subfolders-too?noredirect=1&lq=1,ThunderBird
bb5e4c0d-f964-439c-97b6-bdb9747de3f4,https://support.google.com/chrome/answer/95426?sjid=16867045591165135686-AP,Chrome
7b6c7e24-c58a-49fc-a5bb-d57b80e5b4c3,https://support.google.com/chrome/answer/95647?hl=en&ref_topic=7438325&sjid=16867045591165135686-AP#zippy=%2Cdelete-cookies-from-a-site,Chrome
12086550-11c0-466b-b367-1d9e75b3910e,https://www.quora.com/What-are-the-cool-tricks-to-use-Google-Chrome,Chrome
06fe7178-4491-4589-810f-2e2bc9502122,https://www.wikihow.com/Switch-Tabs-in-Chrome,Chrome
6766f2b8-8a72-417f-a9e5-56fcaa735837,https://support.google.com/chrome/thread/205881926/it-s-possible-to-load-unpacked-extension-automatically-in-chrome?hl=en,Chrome
e1e75309-3ddb-4d09-92ec-de869c928143,https://in5stepstutorials.com/google-chrome/save-web-page-as-pdf-in-chrome.php,Chrome
3d1682a7-0fb0-49ae-a4dc-a73afd2d06d5,https://in5stepstutorials.com/google-chrome/add-change-delete-autofill-address.php,Chrome
35253b65-1c19-4304-8aa4-6884b8218fc0,"https://www.laptopmag.com/articles/how-to-create-desktop-shortcuts-for-web-pages-using-chrome, https://www.reddit.com/r/chrome/comments/13xcbap/crete_shortcut_option_missing/",Chrome
d088f539-cab4-4f9a-ac92-9999fc3a656e,https://medium.com/@inkverseuk2/useful-tips-and-tricks-for-the-google-chrome-browser-ac7d0d24b3cc,Chrome
2ad9387a-65d8-4e33-ad5b-7580065a27ca,https://www.youtube.com/watch?v=IN-Eq_UripQ,Chrome
7a5a7856-f1b6-42a4-ade9-1ca81ca0f263,https://www.youtube.com/watch?v=ZaZ8GcTxjXA,Chrome
3720f614-37fd-4d04-8a6b-76f54f8c222d,https://superuser.com/questions/984668/change-interface-language-of-chrome-to-english,Chrome
b63059a2-53bc-4163-a89f-3ac948c74081,https://superuser.com/questions/1303418/how-do-i-make-chrome-block-absolutely-all-pop-ups?rq=1,Chrome
44ee5668-ecd5-4366-a6ce-c1c9b8d4e938,https://superuser.com/questions/1787991/clear-browsing-history-from-specific-site-on-chrome,Chrome
b5ebc8c6-6329-4373-85b4-9421c97375e9,https://superuser.com/questions/364470/is-there-a-way-to-view-google-chrome-browsing-history-past-three-months-ago?rq=1,Chrome
93eabf48-6a27-4cb6-b963-7d5fe1e0d3a9,https://superuser.com/questions/1417973/how-to-disable-google-chrome-dark-mode,Chrome
2ae9ba84-3a0d-4d4c-8338-3a1478dc5fe3,https://superuser.com/questions/1393683/how-to-change-the-username-in-google-chrome-profiles?rq=1,Chrome
480bcfea-d68f-4aaa-a0a9-2589ef319381,https://bugartisan.medium.com/disable-the-new-chrome-ui-round-in-2023-db168271f71e,Chrome
37b9808f-b2b4-4177-ab00-9ddfae4bad27,https://www.reddit.com/r/chrome/comments/17niw3h/tutorial_how_to_disable_the_download_bubble_in/,Chrome
af630914-714e-4a24-a7bb-f9af687d3b91,https://www.howtogeek.com/680260/how-to-change-chromes-default-text-size/,Chrome
ae78f875-5b98-4907-bbb5-9c737fc68c03,https://support.google.com/chrome/thread/219988391/increase-search-results-per-page?hl=en,Chrome
0ed39f63-6049-43d4-ba4d-5fa2fe04a951,https://www.quora.com/How-do-you-find-and-replace-text-in-Visual-Studio-Code,VS Code
b421106e-b282-4c41-af72-37c95493f95f,https://stackoverflow.com/questions/74153883/launch-vscode-with-new-txt-file,VS Code
53ad5833-3455-407b-bbc6-45b4c79ab8fb,https://www.youtube.com/watch?v=VqCgcpAypFQ,VS Code
eabc805a-bfcf-4460-b250-ac92135819f6,https://www.youtube.com/watch?v=VqCgcpAypFQ,VS Code
3486f395-ad68-459c-8c39-ea07de934dd4,https://www.youtube.com/watch?v=VqCgcpAypFQ,VS Code
982d12a5-beab-424f-8d38-d2a48429e511,https://www.youtube.com/watch?v=ORrELERGIHs,VS Code
4e60007a-f5be-4bfc-9723-c39affa0a6d3,"https://campbell-muscle-lab.github.io/howtos_Python/pages/documentation/best_practices/vscode_docstring_extension/vscode_docstring_extension.html#:~:text=Type%2C%20Ctrl%20%2B%20Shift%20%2B%20P,select%20the%20NumPy%20docstring%20format.",VS Code
e2b5e914-ffe1-44d2-8e92-58f8c5d92bb2,https://superuser.com/questions/1386061/how-to-suppress-some-python-errors-warnings-in-vs-code,VS Code
9439a27b-18ae-42d8-9778-5f68f891805e,https://stackoverflow.com/questions/75832474/how-to-keep-cursor-in-debug-console-when-debugging-in-visual-studio-code,VS Code
ae506c68-352c-4094-9caa-ee9d42052317,https://superuser.com/questions/1460404/get-visual-studio-code-terminal-history?rq=1,VS Code
ea98c5d7-3cf9-4f9b-8ad3-366b58e0fcae,https://superuser.com/questions/1748097/vs-code-disable-tree-view-find-explorer-search,VS Code
c714dcee-cad3-4e12-8f3c-12bdcfcdb048,https://superuser.com/questions/1417361/how-to-disable-file-filtering-in-vs-code-sidebar-explorer?rq=1,VS Code
930fdb3b-11a8-46fe-9bac-577332e2640e,https://superuser.com/questions/1270103/how-to-switch-the-cursor-between-terminal-and-code-in-vscode,VS Code
276cc624-87ea-4f08-ab93-f770e3790175,https://www.quora.com/unanswered/How-do-you-set-the-line-length-in-Visual-Studio-Code,VS Code
9d425400-e9b2-4424-9a4b-d4c7abac4140,https://superuser.com/questions/1466771/is-there-a-way-to-make-editor-tabs-stack-in-vs-code,VS Code
7a4deb26-d57d-4ea9-9a73-630f66a7b568,https://www.quora.com/How-do-I-edit-a-photo-in-GIMP,GIMP
554785e9-4523-4e7a-b8e1-8016f565f56a,https://www.quora.com/How-do-I-edit-a-photo-in-GIMP,GIMP
77b8ab4d-994f-43ac-8930-8ca087d7c4b4,https://superuser.com/questions/1636113/how-to-get-gimp-to-recognize-images-or-pictures-folder-as-the-default-folder-for,GIMP
f4aec372-4fb0-4df5-a52b-79e0e2a5d6ce,https://superuser.com/questions/612338/how-do-i-select-and-move-an-object-in-gimp,GIMP
d52d6308-ec58-42b7-a2c9-de80e4837b2b,https://superuser.com/questions/1447106/how-to-get-rid-of-the-gimp-tool-options-box,GIMP
2a729ded-3296-423d-aec4-7dd55ed5fbb3,https://www.youtube.com/watch?v=lOzSiOIipSM,GIMP
b148e375-fe0b-4bec-90e7-38632b0d73c2,https://www.quora.com/How-do-I-add-layers-in-GIMP,GIMP
a746add2-cab0-4740-ac36-c3769d9bfb46,https://www.youtube.com/watch?v=_L_MMU22bAw,GIMP
7b7617bd-57cc-468e-9c91-40c4ec2bcb3d,https://www.youtube.com/watch?v=G_PjQAy0iiU,GIMP
d16c99dc-2a1e-46f2-b350-d97c86c85c15,https://stackoverflow.com/questions/75185543/use-gimp-to-resize-image-in-one-layer-only,GIMP
573f79b5-abfe-4507-b455-251d45fe6198,https://stackoverflow.com/questions/45196895/gimp-add-padding-to-multiple-images,GIMP
06ca5602-62ca-47f6-ad4f-da151cde54cc,https://stackoverflow.com/questions/74664666/how-to-export-palette-based-png-in-gimp,GIMP
fa9b1e10-4d2d-4a13-af76-7efa822b6a8b,https://stackoverflow.com/questions/24626608/how-to-combine-several-png-images-as-layers-in-a-single-xcf-image,GIMP
6b2b72ed-3a10-4849-876a-750f7cdf3886,https://stackoverflow.com/questions/21018007/resize-image-to-fit-canvas-gimp,GIMP
d0e42fd2-d290-46b3-b598-a6e2b7be9c85,https://stackoverflow.com/questions/56758689/stop-gimp-from-merging-layers-when-de-selecting,GIMP
e2dd0213-26db-4349-abe5-d5667bfd725c,https://superuser.com/questions/839650/how-to-move-an-inserted-text-box-in-gimp,GIMP
f723c744-e62c-4ae6-98d1-750d3cd7d79d,https://www.reddit.com/r/GIMP/comments/12e57w8/how_to_use_gimp_to_exaggerate_contrast/,GIMP
8d6b1c9c-1aab-47fe-9ba5-e84c838d0c57,https://www.quora.com/How-can-email-attachments-be-converted-into-a-word-document-using-Mozilla-Thunderbird,multiple
11e1e614-9696-4d94-88c9-8e556880d41a,https://ifttt.com/applets/L2A89geP-send-chrome-software-update-release-alerts-to-email,multiple
57956154-f0fe-486b-88b8-e7126da035a9,https://zapier.com/apps/email/integrations/google-sheets/547/get-email-notifications-for-new-rows-in-a-google-sheets-spreadsheet,multiple
ec14c524-b245-456d-abd6-ec12c746e9f8,https://zapier.com/apps/gmail/integrations/google-sheets/2618/save-new-gmail-emails-matching-certain-traits-to-a-google-spreadsheet,multiple
cbf5fbda-425e-4619-bcf2-0ea8d4c0bfa3,https://zapier.com/apps/google-sheets/integrations/google-slides/13919/refresh-charts-on-a-google-slides-presentation-when-rows-are-updated-on-google-sheets,multiple
a54284d0-7b93-4327-bfcc-3a421516dbdd,https://superuser.com/questions/655622/cannot-drag-images-from-thunderbird-to-word,multiple
58565672-7bfe-48ab-b828-db349231de6b,https://superuser.com/questions/1792660/open-link-from-other-application-does-not-open-the-url-in-firefox,multiple
6d72aad6-187a-4392-a4c4-ed87269c51cf,https://superuser.com/questions/923171/converting-openoffice-impress-presentation-to-video-without-screen-recording,multiple
937087b6-f668-4ba6-9110-60682ee33441,https://superuser.com/questions/187440/set-default-ubuntu-video-player-as-vlc,multiple
f8cfa149-d1c1-4215-8dac-4a0932bad3c2,https://superuser.com/questions/1803088/libreoffice-calc-clears-clipboard,multiple
5e974913-6905-4c3f-8b65-d7837f3931cc,https://stackoverflow.com/questions/61856141/how-can-i-start-thunderbird-and-minimize-the-window-on-startup-in-ubuntu,multiple
7c179dad-f1c7-4892-b53f-d1c4023d23c7,https://stackoverflow.com/questions/21155085/pasting-excel-tables-in-thunderbird-e-mail-client,multiple
4a68b2dd-70f2-4532-9bc1-d21878bd8cb2,https://stackoverflow.com/questions/65669955/thunderbird-how-to-send-a-mail-to-all-receivers-of-a-folder,multiple
c8457fde-b14b-4aba-b402-144842ea29e1,https://stackoverflow.com/questions/65788200/how-to-open-xlsx-files-in-ms-excel-from-vs-code,multiple
81c425f5-78f3-4771-afd6-3d2973825947,https://www.zyxware.com/articles/3770/how-to-transfer-data-in-libreoffice-calc-to-libreoffice-writer-in-table-format,multiple
bb83cab4-e5c7-42c7-a67b-e46068032b86,https://ask.libreoffice.org/t/save-impress-presentation-as-writer-document/5291/4,multiple
227d2f97-562b-4ccb-ae47-a5ec9e142fbb,https://discourse.gnome.org/t/gimp-and-libre-office-writer/15430/4,multiple
a6bbc08c-51e9-4ee4-9327-83d05075d960,https://forum.openoffice.org/en/forum/viewtopic.php?t=105055,multiple
964e6e03-ba31-466b-8c15-5a351a81f675,https://www.maketecheasier.com/mail-merge-thunderbird-calc/,multiple
2fe4b718-3bd7-46ec-bdce-b184f5653624,https://www.thewindowsclub.com/how-to-create-animated-gif-from-a-video-file-using-vlc-and-gimp,multiple
d02b9364-6bb0-4c7e-9dbd-4db62822bc26,https://stackoverflow.com/questions/38306910/simple-python-script-to-get-a-libreoffice-base-field-and-play-on-vlc,multiple
57fb469b-127a-46fa-8281-bbb3840efdf5,https://support.mozilla.org/en-US/questions/1150626,multiple
3680a5ee-6870-426a-a997-eba929a0d25c,https://unix.stackexchange.com/questions/510850/how-to-open-calc-from-terminal-and-insert-files,multiple
2d8c8a20-6f54-4c2e-ad56-61fbe7af6b78,https://www.quora.com/How-do-I-force-LibreOffice-Calc-to-recalculate-a-spreadsheet-from-the-command-line,multiple
ee9a3c83-f437-4879-8918-be5efbb9fac7,https://stackoverflow.com/questions/64589140/convert-ods-to-csv-using-command-line-when-libreoffice-instance-is-running,multiple
f7dfbef3-7697-431c-883a-db8583a4e4f9,https://www.thegeekdiary.com/libreoffice-command-examples-in-linux/,multiple
2b9493d7-49b8-493a-a71b-56cd1f4d6908,https://devicetests.com/kill-libreoffice-writer-command-line-ubuntu,multiple
51f5801c-18b3-4f25-b0c3-02f85507a078,https://github.com/danielrcollins1/ImpressExtractNotes,multiple
81de345e-5473-4cb6-a74d-b6abf3475a6a,https://stackoverflow.com/questions/45588952/how-can-i-compose-and-send-email-in-thunderbird-from-commandline,multiple
2c9fc0de-3ee7-45e1-a5df-c86206ad78b5,https://nikki-ricks.medium.com/how-to-use-git-add-commit-and-push-in-vs-code-and-command-line-35c0e8c47b62,multiple
510f64c8-9bcc-4be1-8d30-638705850618,https://www.geeksforgeeks.org/how-to-start-vs-code-from-the-terminal-command-line/,multiple
9ff484f7-5c09-4398-ae29-d5904e59e138,https://stackoverflow.com/questions/38606973/playing-opening-and-pausing-vlc-command-line-executed-from-python-scripts,multiple
d9b7c649-c975-4f53-88f5-940b29c47247,https://marketplace.uipath.com/listings/extract-the-first-1000-gmail-emails-from-the-current-month-in-a-new-google-sheets-report,multiple
be4ef0dc-0f70-4936-81d8-3cd2b04482f8,https://marketplace.uipath.com/listings/table-data-extraction-for-sales-opportunities-to-excel-workbook,multiple
78aed49a-a710-4321-a793-b611a7c5b56b,https://marketplace.uipath.com/listings/upload-email-attachments-from-gmail-to-google-drive,multiple
897e3b53-5d4d-444b-85cb-2cdc8a97d903,https://marketplace.uipath.com/listings/convert-word-file-to-pdf-and-store-in-onedrive,multiple
4e9f0faf-2ecc-4ae8-a804-28c9a75d1ddc,https://marketplace.uipath.com/listings/extract-data-from-a-new-invoice-file-in-google-drive-and-store-it-in-google-sheets4473,multiple
b52b40a5-ad70-4c53-b5b0-5650a8387052,https://marketplace.uipath.com/listings/merge-pdfs-from-gmail-email-attachments-and-upload-to-gogle-drive,multiple
46407397-a7d5-4c6b-92c6-dbe038b1457b,https://marketplace.uipath.com/listings/upload-to-google-drive-images-from-pdf-attachments-received-via-gmail,multiple
a0b9dc9c-fc07-4a88-8c5d-5e3ecad91bcb,https://marketplace.uipath.com/listings/backup-important-emails-to-onedrive-or-sharepoint,multiple
665f4af1-617d-4009-baff-84ff66071e6a,https://www.howtogeek.com/663927/how-to-open-google-chrome-using-command-prompt-on-windows-10/#open-chrome-straight-to-a-specific-website,multiple
e6313b30-3903-4ed9-8c7d-4c47bf51fc96,https://stackoverflow.com/questions/12258086/how-do-i-run-google-chrome-as-root,multiple
1 id Source InvolvedApp
2 94d95f96-9699-4208-98ba-3c3119edf9c2 https://help.ubuntu.com/lts/ubuntu-help/addremove-install.html.en OS
3 bedcedc4-4d72-425e-ad62-21960b11fe0d https://www.youtube.com/watch?v=D4WyNjt_hbQ&t=2s OS
4 43c2d64c-bab5-4dcb-a30c-b888321c319a https://ubuntu.com/tutorials/command-line-for-beginners#4-creating-folders-and-files OS
5 7688b85f-87a4-4e4a-b2f8-f3d6c3f29b82 https://ubuntu.com/tutorials/command-line-for-beginners#5-moving-and-manipulating-files OS
6 ec4e3f68-9ea4-4c18-a5c9-69f89d1178b3 https://www.youtube.com/watch?v=D4WyNjt_hbQ&t=2s OS
7 a462a795-fdc7-4b23-b689-e8b6df786b78 https://help.ubuntu.com/lts/ubuntu-help/shell-exit.html.en OS
8 f9be0997-4b7c-45c5-b05c-4612b44a6118 https://help.ubuntu.com/lts/ubuntu-help/shell-notifications.html.en OS
9 ae039631-2b12-4637-84f6-c67d51511be3 https://help.ubuntu.com/lts/ubuntu-help/net-default-browser.html.en OS
10 e2eb4bf1-aa93-4192-b55d-03e2fb6dfd15 https://help.ubuntu.com/lts/ubuntu-help/contacts-add-remove.html.en OS
11 28cc3b7e-b194-4bc9-8353-d04c0f4d56d2 https://help.ubuntu.com/lts/ubuntu-help/sound-volume.html.en OS
12 5ea617a3-0e86-4ba6-aab2-dac9aa2e8d57 https://help.ubuntu.com/lts/ubuntu-help/files-recover.html.en OS
13 e0df059f-28a6-4169-924f-b9623e7184cc https://help.ubuntu.com/lts/ubuntu-help/files-rename.html.en OS
14 ddc75b62-7311-4af8-bfb3-859558542b36 https://help.ubuntu.com/lts/ubuntu-help/addremove-remove.html.en OS
15 5c433d22-ed9a-4e31-91f5-54cf3e8acd63 https://help.ubuntu.com/lts/ubuntu-help/session-language.html.zh-CN OS
16 b6781586-6346-41cd-935a-a6b1487918fc https://help.ubuntu.com/lts/ubuntu-help/clock-timezone.html.en OS
17 b3d4a89c-53f2-4d6b-8b6a-541fb5d205fa https://help.ubuntu.com/lts/ubuntu-help/bluetooth-turn-on-off.html.en OS
18 3ce045a0-877b-42aa-8d2c-b4a863336ab8 https://help.ubuntu.com/lts/ubuntu-help/a11y-font-size.html.en OS
19 fe41f596-a71b-4c2f-9b2f-9dcd40b568c3 https://help.ubuntu.com/lts/ubuntu-help/power-percentage.html.en OS
20 a4d98375-215b-4a4d-aee9-3d4370fccc41 https://help.ubuntu.com/lts/ubuntu-help/privacy-screen-lock.html.en OS
21 765d2b74-88a7-4d50-bf51-34e4106fd24a https://help.ubuntu.com/lts/ubuntu-help/files-delete.html.en OS
22 cc9d4f34-1ca0-4a1b-8ff2-09302696acb9 https://superuser.com/questions/178587/how-do-i-detach-a-process-from-terminal-entirely OS
23 5812b315-e7bd-4265-b51f-863c02174c28 https://superuser.com/questions/149404/create-an-ssh-user-who-only-has-permission-to-access-specific-folders OS
24 c56de254-a3ec-414e-81a6-83d2ce8c41fa https://superuser.com/questions/28426/how-to-extract-text-with-ocr-from-a-pdf-on-linux OS
25 6ebbfb01-ea72-4226-a2a6-dc428e111ed2 https://superuser.com/questions/46748/how-do-i-make-bash-my-default-shell-on-ubuntu OS
26 4d2b519e-e872-4100-8ea3-fe71ab0f9133 https://stackoverflow.com/questions/11530090/adding-a-new-entry-to-the-path-variable-in-zsh OS
27 c288e301-e626-4b98-a1ab-159dcb162af5 https://stackoverflow.com/questions/41986507/unable-to-set-default-python-version-to-python3-in-ubuntu OS
28 13584542-872b-42d8-b299-866967b5c3ef https://superuser.com/questions/72176/linux-set-default-terminal-size-and-screen-position OS
29 23393935-50c7-4a86-aeea-2b78fd089c5c https://superuser.com/questions/91307/copying-only-jpg-from-a-directory-structure-to-another-location-linux OS
30 f10b16e1-c160-4cb3-989f-7b2ec89bc073 https://www.wikihow.com/Install-Gnome-on-Ubuntu OS
31 eb03d19a-b88d-4de4-8a64-ca0ac66f426b https://www.youtube.com/shorts/t9JLUaT55UQ MS Excel
32 0bf05a7d-b28b-44d2-955a-50b41e24012a https://www.youtube.com/shorts/FPAQaDTS8VY MS Excel
33 7b802dad-6e0f-4204-9815-d4e3f57627d8 https://www.youtube.com/shorts/Of-lzeP1usE MS Excel
34 7a4e4bc8-922c-4c84-865c-25ba34136be1 https://www.youtube.com/shorts/bvUhr1AHs44 MS Excel
35 2bd59342-0664-4ccb-ba87-79379096cc08 https://www.youtube.com/shorts/L3Z-F1QTQFY MS Excel
36 a9f325aa-8c05-4e4f-8341-9e4358565f4f https://www.youtube.com/shorts/A0gmEBRKXWs MS Excel
37 ecb0df7a-4e8d-4a03-b162-053391d3afaf https://www.youtube.com/shorts/tXOovKn0H68 MS Excel
38 7efeb4b1-3d19-4762-b163-63328d66303b https://www.youtube.com/shorts/4jzXfZNhfmk MS Excel
39 4e6fcf72-daf3-439f-a232-c434ce416af6 https://www.youtube.com/shorts/0uxJccNCKcE MS Excel
40 6054afcb-5bab-4702-90a0-b259b5d3217c https://www.youtube.com/shorts/JTbZ8sRxkdU MS Excel
41 abed40dc-063f-4598-8ba5-9fe749c0615d https://www.youtube.com/shorts/xgf4ZpsEx5M MS Excel
42 01b269ae-2111-4a07-81fd-3fcd711993b0 https://www.youtube.com/shorts/VrUzPTIwQ04 MS Excel
43 8b1ce5f2-59d2-4dcc-b0b0-666a714b9a14 https://www.youtube.com/shorts/Hbcwu6IQ1ns MS Excel
44 af2b02f7-acee-4be4-8b66-499fab394915 https://www.youtube.com/shorts/AwKsb5VmtBI MS Excel
45 da1d63b8-fa12-417b-ba18-f748e5f770f3 https://www.youtube.com/shorts/hquscnbz2-U MS Excel
46 636380ea-d5f6-4474-b6ca-b2ed578a20f1 https://www.youtube.com/shorts/_BYL6VOHLGw MS Excel, Edge
47 5ba77536-05c5-4aae-a9ff-6e298d094c3e https://www.youtube.com/shorts/CuBC1evUS5I MS Excel
48 4bc4eaf4-ca5e-4db2-8138-8d4e65af7c0b https://www.youtube.com/shorts/1adQWfjN-tI MS Excel
49 672a1b02-c62f-4ae2-acf0-37f5fb3052b0 https://www.youtube.com/shorts/2rhdQXI4Lng MS Excel
50 648fe544-16ba-44af-a587-12ccbe280ea6 https://www.youtube.com/shorts/sOPBMWaC6Uc MS Excel
51 8985d1e4-5b99-4711-add4-88949ebb2308 https://www.youtube.com/shorts/J5ts2Acv9Pc MS Excel
52 9e606842-2e27-43bf-b1d1-b43289c9589b https://www.youtube.com/shorts/B-mGYDFOyUs MS Excel
53 fcb6e45b-25c4-4087-9483-03d714f473a9 https://www.youtube.com/shorts/GZipp7nOZS0 MS Excel
54 68c0c5b7-96f3-4e87-92a7-6c1b967fd2d2 https://www.youtube.com/shorts/JEH5TsK-cCk MS Excel, Edge
55 fff629ea-046e-4793-8eec-1a5a15c3eb35 https://www.youtube.com/shorts/8WybtCdUT6w MS Excel
56 5c9a206c-bb00-4fb6-bb46-ee675c187df5 https://www.youtube.com/shorts/VbQtMNnq9i4 MS Excel
57 e975ae74-79bd-4672-8d1c-dc841a85781d https://www.youtube.com/shorts/GjT7gGe5Sr8 MS Excel
58 34a6938a-58da-4897-8639-9b90d6db5391 https://www.youtube.com/shorts/gW37x2TkzOY MS Excel
59 b5a22759-b4eb-4bf2-aeed-ad14e8615f19 https://www.youtube.com/shorts/3xLa-D0C7Ic MS Excel
60 2f9913a1-51ed-4db6-bfe0-7e1c95b3139e https://www.youtube.com/shorts/dGLRcmfVO6Q MS Excel
61 2558031e-401d-4579-8e00-3ecf540fb492 https://www.mrexcel.com/board/threads/sales-for-the-first-6-weeks.1249213/ MS Excel
62 39aa4e37-dc91-482e-99af-132a612d40f3 https://www.libreofficehelp.com/add-insert-delete-copy-move-rename-a-worksheet-in-libreoffice-calc/ LibreOffice Calc
63 0cecd4f3-74de-457b-ba94-29ad6b5dafb6 https://www.libreofficehelp.com/add-insert-delete-copy-move-rename-a-worksheet-in-libreoffice-calc/ LibreOffice Calc
64 4188d3a4-077d-46b7-9c86-23e1a036f6c1 https://www.libreofficehelp.com/freeze-unfreeze-rows-columns-ranges-calc/ LibreOffice Calc
65 51b11269-2ca8-4b2a-9163-f21758420e78 https://www.reddit.com/r/LibreOfficeCalc/comments/186pcc6/how_to_arrange_numbers_in_a_column_from_minimum/ LibreOffice Calc
66 7e429b8d-a3f0-4ed0-9b58-08957d00b127 https://medium.com/@divyangichaudhari17/how-to-use-vlookup-and-hlookup-in-libre-calc-3370698bb3ff LibreOffice Calc
67 f5a90742-3fa2-40fc-a564-f29b054e0337 https://superuser.com/questions/1236149/libreoffice-calc-how-to-apply-functions-to-columns LibreOffice Calc
68 22df9241-f8d7-4509-b7f1-37e501a823f7 https://superuser.com/questions/1767185/how-do-you-move-cells-in-libreoffice-calc LibreOffice Calc
69 1434ca3e-f9e3-4db8-9ca7-b4c653be7d17 https://www.wikihow.com/Remove-Duplicates-in-Open-Office-Calc LibreOffice Calc
70 347ef137-7eeb-4c80-a3bb-0951f26a8aff https://www.youtube.com/watch?v=bgO40-CjYNY LibreOffice Calc
71 6e99a1ad-07d2-4b66-a1ce-ece6d99c20a5 https://www.youtube.com/watch?v=nl-nXjJurhQ LibreOffice Calc
72 3aaa4e37-dc91-482e-99af-132a612d40f3 https://www.quora.com/How-can-you-import-export-CSV-files-with-LibreOffice-Calc-or-OpenOffice LibreOffice Calc
73 0decd4f3-74de-457b-ba94-29ad6b5dafb6 https://justclickhere.co.uk/resources/checkboxes-tick-boxes-libreoffice-calc/ LibreOffice Calc
74 37608790-6147-45d0-9f20-1137bb35703d https://www.youtube.com/shorts/uzPo_CPCHH8 MS Excel
75 f9584479-3d0d-4c79-affa-9ad7afdd8850 https://youtube.com/shorts/feldd-Pn48c?si=9xJiem2uAHm6Jshb LibreOffice Calc
76 d681960f-7bc3-4286-9913-a8812ba3261a https://www.youtube.com/shorts/d7U1S_IsTVM LibreOffice Calc
77 f6a90742-3fa2-40fc-a564-f29b054e0337 https://www.excel-easy.com/examples/drop-down-list.html LibreOffice Calc
78 21df9241-f8d7-4509-b7f1-37e501a823f7 https://www.youtube.com/watch?v=p5C4V_AO1UU LibreOffice Calc
79 1334ca3e-f9e3-4db8-9ca7-b4c653be7d17 https://techcommunity.microsoft.com/t5/excel/excel-workbook-top-way-too-big-can-t-see-rows-and-columns/m-p/4014694 LibreOffice Calc
80 357ef137-7eeb-4c80-a3bb-0951f26a8aff https://www.reddit.com/r/excel/comments/17zny8u/calculating_total_amount_earned_from_total_hours/ LibreOffice Calc
81 6f99a1ad-07d2-4b66-a1ce-ece6d99c20a5 https://techcommunity.microsoft.com/t5/excel/sumarize-the-sheetnames/m-p/4014716 LibreOffice Calc
82 aa3a8974-2e85-438b-b29e-a64df44deb4b https://www.quora.com/Libre-Office-Calc-How-do-I-resize-all-cells-in-a-sheet-to-make-them-fit-to-1-page-for-printing-and-exporting-as-PDF LibreOffice Calc
83 a01fbce3-2793-461f-ab86-43680ccbae25 https://superuser.com/questions/1250677/how-to-set-decimal-separator-in-libre-office-calc LibreOffice Calc
84 4f07fbe9-70de-4927-a4d5-bb28bc12c52c https://superuser.com/questions/1081048/libreoffice-calc-how-to-pad-number-to-fixed-decimals-when-used-within-formula LibreOffice Calc
85 e3b1d5fa-ed00-4129-bda1-1452bd2b6772 https://www.reddit.com/r/libreoffice/comments/tel112/calc_how_to_calculate_sum_by_categories/ LibreOffice Calc
86 ca6a9524-f8e9-4d2f-9364-ab0cad567739 https://www.reddit.com/r/libreoffice/comments/113gmyc/how_to_remove_certain_text_from_cells_in_calc/ LibreOffice Calc
87 a455e8d0-930f-40d2-9575-5e8d2d222f58 https://superuser.com/questions/562944/quickly-fill-blank-cells-in-a-list-in-libreoffice-calc LibreOffice Calc
88 83ee22c6-7737-49ce-9b5a-138c3e92af04 https://superuser.com/questions/661102/currency-conversion-in-libreoffice-calc LibreOffice Calc
89 819f61c2-ec77-4d3f-9996-0838ae5aacc8 https://superuser.com/questions/381696/creating-a-column-of-working-days-in-libreoffice-calc LibreOffice Calc
90 69d577b3-004e-4bca-89b2-0d7c2f6049e3 https://superuser.com/questions/387106/libreoffice-calc-how-to-get-total-for-hhmmss-cells LibreOffice Calc
91 0a1bf4ca-d4ea-4618-baa5-6e8dc1b46d82 https://superuser.com/questions/571915/sum-up-to-n-highest-value-out-of-a-series LibreOffice Calc
92 ac9bb6cb-1888-43ab-81e4-a98a547918cd https://superuser.com/questions/1674211/how-to-change-colour-of-slide-number-in-libre-office LibreOffice Impress
93 5d901039-a89c-4bfb-967b-bf66f4df075e https://superuser.com/questions/986776/how-can-i-stretch-an-image-in-a-libreoffice-impress-presentation-to-fill-the-pag LibreOffice Impress
94 071d4ace-091a-4ec3-886e-f4be55ae375d https://superuser.com/questions/706860/hide-slide-numbers-and-slide-footer-on-first-and-second-slide-in-libreoffice-imp?rq=1 LibreOffice Impress
95 550ce7e7-747b-495f-b122-acdc4d0b8e54 https://technical-tips.com/blog/software/text-in-libreoffice-strikethrough--6948#:~:text=To%20strikethrough%20Text%20in%20LibreOffice%201%20In%20your,effect%22%20can%20your%20additionally%2C%20for%20example%2C%20double%20underline. LibreOffice Impress
96 455d3c66-7dc6-4537-a39a-36d3e9119df7 https://www.libreofficehelp.com/export-libreoffice-impress-slides-images/#:~:text=Exporting%20a%20single%20slide%20as.jpg%2C.png%2C%20etc%20image%20is,on%20the%20checkbox%20Selection.%20Provide%20jpg%20quality%20options. LibreOffice Impress
97 af23762e-2bfd-4a1d-aada-20fa8de9ce07 https://superuser.com/questions/1059080/how-to-make-a-summary-slide-in-impress-listing-the-titles-of-all-slides-autom LibreOffice Impress
98 c59742c0-4323-4b9d-8a02-723c251deaa0 https://www.reddit.com/r/libreoffice/comments/17lcdrp/audio_not_supported_in_libreoffice_impress/ LibreOffice Impress
99 39478d4a-1049-456f-aa77-407811393add https://www.reddit.com/r/libreoffice/comments/jul3o8/putting_cap_or_hat_or_carat_symbol_in_libre/ LibreOffice Impress
100 c3ad4442-499f-4e58-bc4e-1a1417ea9b8c http://maharajacollege.ac.in/material/Libreofficeimpresspdf.pdf LibreOffice Impress
101 ef9d12bd-bcee-4ba0-a40e-918400f43ddf https://www.reddit.com/r/libreoffice/comments/18elh3y/i_closed_the_slide_pannel_on_the_left_and_idk_how/ LibreOffice Impress
102 9ec204e4-f0a3-42f8-8458-b772a6797cab https://www.tiktok.com/@lil.d1rt_/video/7247574148887629083 LibreOffice Impress
103 0f84bef9-9790-432e-92b7-eece357603fb https://stackoverflow.com/questions/29036788/how-to-disable-libreoffice-impress-to-use-multiple-display LibreOffice Impress
104 ce88f674-ab7a-43da-9201-468d38539e4a https://justclickhere.co.uk/resources/change-slides-in-impress-to-portrait/ LibreOffice Impress
105 f0a334af-f91b-4c03-b578-aac9bec2b543 https://www.libreofficehelp.com/insert-video-impress-presentation/#Inserting_a_Video_in_Impress LibreOffice Impress
106 3b27600c-3668-4abd-8f84-7bcdebbccbdb https://www.libreofficehelp.com/change-slide-background-impress/#All_Slides LibreOffice Impress
107 a097acff-6266-4291-9fbd-137af7ecd439 https://www.youtube.com/watch?v=DDmEvjs4iBw LibreOffice Impress
108 21760ecb-8f62-40d2-8d85-0cee5725cb72 https://www.libreofficehelp.com/add-animations-transitions-libreoffice-impress-slides/ LibreOffice Impress
109 3cc4f35d-fa2e-4555-afb9-741b7c062a74 https://documentation.libreoffice.org/assets/Uploads/Documentation/en/IG7.6/IG76-ImpressGuide.pdf LibreOffice Impress
110 6ada715d-3aae-4a32-a6a7-429b2e43fb93 https://www.quora.com/How-do-you-insert-images-into-a-LibreOffice-Writer-document LibreOffice Writer
111 ecc2413d-8a48-416e-a3a2-d30106ca36cb https://www.quora.com/How-can-I-insert-a-blank-page-on-libreoffice LibreOffice Writer
112 0e47de2a-32e0-456c-a366-8c607ef7a9d2 https://ask.libreoffice.org/t/how-to-start-page-numbering-on-a-certain-page/39931/4 LibreOffice Writer
113 4bcb1253-a636-4df4-8cb0-a35c04dfef31 https://www.libreofficehelp.com/save-export-writer-documents-in-pdf-epub-format/ LibreOffice Writer
114 0810415c-bde4-4443-9047-d5f70165a697 https://www.youtube.com/watch?v=Q_AaL6ljudU LibreOffice Writer
115 e528b65e-1107-4b8c-8988-490e4fece599 https://www.youtube.com/watch?v=l25Evu4ohKg LibreOffice Writer
116 66399b0d-8fda-4618-95c4-bfc6191617e9 https://www.youtube.com/watch?v=l25Evu4ohKg LibreOffice Writer
117 936321ce-5236-426a-9a20-e0e3c5dc536f https://www.youtube.com/watch?v=l25Evu4ohKg LibreOffice Writer
118 663876c7-3471-43db-ba51-f410b13d9d7d https://askubuntu.com/questions/319593/how-to-type-science-equations-in-libre-office LibreOffice Writer
119 3ef2b351-8a84-4ff2-8724-d86eae9b842e https://askubuntu.com/questions/1066351/how-do-you-center-align-in-libreoffice#:~:text=Ctrl%20%2B%20e%20will%20Center%20align%20the%20cursor%20for%20you. LibreOffice Writer
120 45d61a06-6545-4422-97b7-bc76cfa964c1 https://stackoverflow.com/questions/71685737/how-to-replace-all-newlines-with-paragraph-marks-in-libreoffice-write LibreOffice Writer
121 0b17a146-2934-46c7-8727-73ff6b6483e8 https://askubuntu.com/questions/245695/how-do-you-insert-subscripts-and-superscripts-into-ordinary-non-formula-text-i LibreOffice Writer
122 0e763496-b6bb-4508-a427-fad0b6c3e195 https://ask.libreoffice.org/t/how-do-i-change-the-font-for-the-whole-document-in-writer/9220 LibreOffice Writer
123 f178a4a9-d090-4b56-bc4c-4b72a61a035d https://ask.libreoffice.org/t/how-do-i-make-times-new-roman-the-default-font-in-lo/64604 LibreOffice Writer
124 0a0faba3-5580-44df-965d-f562a99b291c https://stackoverflow.com/questions/64528055/how-to-make-part-of-my-sentence-left-aligned-and-rest-as-right-aligned LibreOffice Writer
125 e246f6d8-78d7-44ac-b668-fcf47946cb50 https://ask.libreoffice.org/t/how-to-change-text-size-color-of-italic-font/77712 LibreOffice Writer
126 8472fece-c7dd-4241-8d65-9b3cd1a0b568 https://stackoverflow.com/questions/37259827/libreoffice-writer-how-to-set-different-colors-to-each-letter LibreOffice Writer
127 88fe4b2d-3040-4c70-9a70-546a47764b48 https://stackoverflow.com/questions/56554555/libreoffice-writer-how-to-create-empty-line-space-after-every-period-in-a-par LibreOffice Writer
128 6a33f9b9-0a56-4844-9c3f-96ec3ffb3ba2 https://superuser.com/questions/762500/how-do-i-find-all-highlighted-text-in-libreoffice-writer LibreOffice Writer
129 d53ff5ee-3b1a-431e-b2be-30ed2673079b https://ask.libreoffice.org/t/how-to-convert-all-uppercase-to-lowercase/53341 LibreOffice Writer
130 72b810ef-4156-4d09-8f08-a0cf57e7cefe https://superuser.com/questions/657792/libreoffice-writer-how-to-apply-strikethrough-text-formatting?rq=1 LibreOffice Writer
131 6f81754e-285d-4ce0-b59e-af7edb02d108 https://superuser.com/questions/789473/remove-duplicate-lines-in-libreoffice-openoffice-writer LibreOffice Writer
132 41c621f7-3544-49e1-af8d-dafd0f834f75 https://superuser.com/questions/1668018/how-to-auto-format-lines-in-libre-office-writer LibreOffice Writer
133 b21acd93-60fd-4127-8a43-2f5178f4a830 https://superuser.com/questions/1097199/how-can-i-double-space-a-document-in-libreoffice?rq=1 LibreOffice Writer
134 59f21cfb-0120-4326-b255-a5b827b38967 https://docs.videolan.me/vlc-user/desktop/3.0/en/basic/media.html#playing-a-file VLC player
135 8ba5ae7a-5ae5-4eab-9fcc-5dd4fe3abf89 https://docs.videolan.me/vlc-user/desktop/3.0/en/basic/recording/playing.html#choose-your-recordings-folder VLC player
136 8f080098-ddb1-424c-b438-4e96e5e4786e https://medium.com/@jetscribe_ai/how-to-extract-mp3-audio-from-videos-using-vlc-media-player-beeef644ebfb VLC player
137 bba3381f-b5eb-4439-bd9e-80c22218d5a7 https://www.quora.com/How-do-I-play-online-videos-using-the-VLC-media-player VLC player
138 a1c3ab35-02de-4999-a7ed-2fd12c972c6e https://www.quora.com/How-do-I-compress-a-video-with-VLC VLC player
139 fba2c100-79e8-42df-ae74-b592418d54f4 https://www.youtube.com/watch?v=XHprwDJ0-fU&t=436s VLC player
140 d70666e4-7348-42c7-a06a-664094c5df3c https://www.youtube.com/watch?v=XHprwDJ0-fU&t=436s VLC player
141 efcf0d81-0835-4880-b2fd-d866e8bc2294 https://www.youtube.com/watch?v=XHprwDJ0-fU&t=436s, https://help.ubuntu.com/stable/ubuntu-help/look-background.html.en VLC player
142 8d9fd4e2-6fdb-46b0-b9b9-02f06495c62f https://www.youtube.com/watch?v=XHprwDJ0-fU&t=436s VLC player
143 aa4b5023-aef6-4ed9-bdc9-705f59ab9ad6 https://videoconverter.wondershare.com/vlc/how-to-rotate-a-video-using-vlc.html?gad_source=1&gclid=CjwKCAiA-vOsBhAAEiwAIWR0TaGSOLkYiBeVQGZSyfeUP3g-tIvYxffl5RFIu0-zrUL1IF41eCw1JRoCnCMQAvD_BwE VLC player
144 386dbd0e-0241-4a0a-b6a2-6704fba26b1c https://superuser.com/questions/1708415/pause-and-play-vlc-in-background?rq=1 VLC player
145 9195653c-f4aa-453d-aa95-787f6ccfaae9 https://superuser.com/questions/1513285/how-can-i-increase-the-maximum-volume-output-by-vlc?rq=1 VLC player
146 5ac2891a-eacd-4954-b339-98abba077adb https://superuser.com/questions/1412810/how-to-prevent-vlc-media-player-from-auto-closing-after-video-end#:%7E:text=Click%20on%20%22Media%22on%20the,VLC%20player%20after%20video%20ending VLC player
147 0d95d28a-9587-433b-a805-1fbe5467d598 https://superuser.com/questions/1299036/vlc-how-to-open-the-folder-of-the-current-playing-video?noredirect=1&lq=1 VLC player
148 d06f0d4d-2cd5-4ede-8de9-598629438c6e https://superuser.com/questions/1039392/changing-colour-of-vlc-volume-slider VLC player
149 a5bbbcd5-b398-4c91-83d4-55e1e31bbb81 https://superuser.com/questions/776056/how-to-hide-bottom-toolbar-in-vlc VLC player
150 f3977615-2b45-4ac5-8bba-80c17dbe2a37 https://www.reddit.com/r/Fedora/comments/rhljzd/how_to_run_multiple_instances_of_vlc_media_player/ VLC player
151 c669a35f-d45a-450e-b1f2-f473748337bb https://www.quora.com/How-do-I-fast-forward-a-video-in-VLC-player VLC player
152 d1ba14d0-fef8-4026-8418-5b581dc68ca0 https://superuser.com/questions/306154/how-to-use-a-b-repeat-feature-of-vlc VLC player
153 215dfd39-f493-4bc3-a027-8a97d72c61bf https://superuser.com/questions/1224784/how-to-change-vlcs-splash-screen VLC player
154 bb5e4c0d-f964-439c-97b6-bdb9747de3f4 https://www.wikihow.com/Remove-an-Email-Account-from-Thunderbird ThunderBird
155 7b6c7e24-c58a-49fc-a5bb-d57b80e5b4c3 https://www.wikihow.com/Access-Gmail-With-Mozilla-Thunderbird ThunderBird
156 b188fe10-ae67-4db8-a154-26a0b8ff8f1e https://www.reddit.com/r/Thunderbird/comments/17vv2os/restore_readability_in_message_list_pane/ ThunderBird
157 12086550-11c0-466b-b367-1d9e75b3910e https://www.bitrecover.com/blog/manage-thunderbird-profiles/ ThunderBird
158 06fe7178-4491-4589-810f-2e2bc9502122 https://www.quora.com/How-do-I-backup-email-files-in-Mozilla-Thunderbird ThunderBird
159 6766f2b8-8a72-417f-a9e5-56fcaa735837 https://www.adsigner.com/user-manual/signatures/setup-email-client-thunderbird/#:~:text=is%20probably%20hidden.-,Right%20click%20on%20the%20empty%20space%20at%20the%20top%20of,signature%20from%20a%20file%20instead. ThunderBird
160 e1e75309-3ddb-4d09-92ec-de869c928143 https://support.mozilla.org/en-US/kb/organize-your-messages-using-filters ThunderBird
161 3d1682a7-0fb0-49ae-a4dc-a73afd2d06d5 https://support.mozilla.org/en-US/kb/organize-your-messages-using-filters ThunderBird
162 35253b65-1c19-4304-8aa4-6884b8218fc0 https://support.mozilla.org/en-US/questions/1259354 ThunderBird
163 d088f539-cab4-4f9a-ac92-9999fc3a656e https://support.mozilla.org/en-US/kb/how-use-attachments ThunderBird
164 2ad9387a-65d8-4e33-ad5b-7580065a27ca https://support.mozilla.org/bm/questions/1027435, https://www.wikihow.tech/Create-Folders-in-Mozilla-Thunderbird ThunderBird
165 480bcfea-d68f-4aaa-a0a9-2589ef319381 https://www.reddit.com/r/Thunderbird/comments/182dg5p/unified_inbox_howto/ ThunderBird
166 37b9808f-b2b4-4177-ab00-9ddfae4bad27 https://www.quora.com/How-can-I-schedule-Mozilla-Thunderbird-to-turn-off-automatically ThunderBird
167 af630914-714e-4a24-a7bb-f9af687d3b91 https://stackoverflow.com/questions/11333148/adding-a-toolbar-button-to-a-thundebird-compose-message-window?rq=3 ThunderBird
168 3299584d-8f11-4457-bf4c-ce98f7600250 https://superuser.com/questions/1643561/would-like-to-see-the-email-address-from-sender-in-the-column ThunderBird
169 030eeff7-b492-4218-b312-701ec99ee0cc https://superuser.com/questions/1781004/how-do-i-remove-the-indentation-and-character-in-quoted-text-of-a-reply-mess ThunderBird
170 94760984-3ff5-41ee-8347-cf1af709fea0 https://superuser.com/questions/1757333/how-can-i-view-thunderbird-in-full-dark-mode ThunderBird
171 99146c54-4f37-4ab8-9327-5f3291665e1e https://superuser.com/questions/1764409/how-to-send-email-with-thunderbird-without-configuring-an-incoming-email-service ThunderBird
172 9656a811-9b5b-4ddf-99c7-5117bcef0626 https://superuser.com/questions/205240/is-there-a-way-to-get-a-popup-confirmation-box-when-you-send-an-email-in-thunder?rq=1 ThunderBird
173 c9e7eaf2-b1a1-4efc-a982-721972fa9f02 https://superuser.com/questions/544480/how-to-apply-automatic-message-filters-to-subfolders-too?noredirect=1&lq=1 ThunderBird
174 bb5e4c0d-f964-439c-97b6-bdb9747de3f4 https://support.google.com/chrome/answer/95426?sjid=16867045591165135686-AP Chrome
175 7b6c7e24-c58a-49fc-a5bb-d57b80e5b4c3 https://support.google.com/chrome/answer/95647?hl=en&ref_topic=7438325&sjid=16867045591165135686-AP#zippy=%2Cdelete-cookies-from-a-site Chrome
176 12086550-11c0-466b-b367-1d9e75b3910e https://www.quora.com/What-are-the-cool-tricks-to-use-Google-Chrome Chrome
177 06fe7178-4491-4589-810f-2e2bc9502122 https://www.wikihow.com/Switch-Tabs-in-Chrome Chrome
178 6766f2b8-8a72-417f-a9e5-56fcaa735837 https://support.google.com/chrome/thread/205881926/it-s-possible-to-load-unpacked-extension-automatically-in-chrome?hl=en Chrome
179 e1e75309-3ddb-4d09-92ec-de869c928143 https://in5stepstutorials.com/google-chrome/save-web-page-as-pdf-in-chrome.php Chrome
180 3d1682a7-0fb0-49ae-a4dc-a73afd2d06d5 https://in5stepstutorials.com/google-chrome/add-change-delete-autofill-address.php Chrome
181 35253b65-1c19-4304-8aa4-6884b8218fc0 https://www.laptopmag.com/articles/how-to-create-desktop-shortcuts-for-web-pages-using-chrome, https://www.reddit.com/r/chrome/comments/13xcbap/crete_shortcut_option_missing/ Chrome
182 d088f539-cab4-4f9a-ac92-9999fc3a656e https://medium.com/@inkverseuk2/useful-tips-and-tricks-for-the-google-chrome-browser-ac7d0d24b3cc Chrome
183 2ad9387a-65d8-4e33-ad5b-7580065a27ca https://www.youtube.com/watch?v=IN-Eq_UripQ Chrome
184 7a5a7856-f1b6-42a4-ade9-1ca81ca0f263 https://www.youtube.com/watch?v=ZaZ8GcTxjXA Chrome
185 3720f614-37fd-4d04-8a6b-76f54f8c222d https://superuser.com/questions/984668/change-interface-language-of-chrome-to-english Chrome
186 b63059a2-53bc-4163-a89f-3ac948c74081 https://superuser.com/questions/1303418/how-do-i-make-chrome-block-absolutely-all-pop-ups?rq=1 Chrome
187 44ee5668-ecd5-4366-a6ce-c1c9b8d4e938 https://superuser.com/questions/1787991/clear-browsing-history-from-specific-site-on-chrome Chrome
188 b5ebc8c6-6329-4373-85b4-9421c97375e9 https://superuser.com/questions/364470/is-there-a-way-to-view-google-chrome-browsing-history-past-three-months-ago?rq=1 Chrome
189 93eabf48-6a27-4cb6-b963-7d5fe1e0d3a9 https://superuser.com/questions/1417973/how-to-disable-google-chrome-dark-mode Chrome
190 2ae9ba84-3a0d-4d4c-8338-3a1478dc5fe3 https://superuser.com/questions/1393683/how-to-change-the-username-in-google-chrome-profiles?rq=1 Chrome
191 480bcfea-d68f-4aaa-a0a9-2589ef319381 https://bugartisan.medium.com/disable-the-new-chrome-ui-round-in-2023-db168271f71e Chrome
192 37b9808f-b2b4-4177-ab00-9ddfae4bad27 https://www.reddit.com/r/chrome/comments/17niw3h/tutorial_how_to_disable_the_download_bubble_in/ Chrome
193 af630914-714e-4a24-a7bb-f9af687d3b91 https://www.howtogeek.com/680260/how-to-change-chromes-default-text-size/ Chrome
194 ae78f875-5b98-4907-bbb5-9c737fc68c03 https://support.google.com/chrome/thread/219988391/increase-search-results-per-page?hl=en Chrome
195 0ed39f63-6049-43d4-ba4d-5fa2fe04a951 https://www.quora.com/How-do-you-find-and-replace-text-in-Visual-Studio-Code VS Code
196 b421106e-b282-4c41-af72-37c95493f95f https://stackoverflow.com/questions/74153883/launch-vscode-with-new-txt-file VS Code
197 53ad5833-3455-407b-bbc6-45b4c79ab8fb https://www.youtube.com/watch?v=VqCgcpAypFQ VS Code
198 eabc805a-bfcf-4460-b250-ac92135819f6 https://www.youtube.com/watch?v=VqCgcpAypFQ VS Code
199 3486f395-ad68-459c-8c39-ea07de934dd4 https://www.youtube.com/watch?v=VqCgcpAypFQ VS Code
200 982d12a5-beab-424f-8d38-d2a48429e511 https://www.youtube.com/watch?v=ORrELERGIHs VS Code
201 4e60007a-f5be-4bfc-9723-c39affa0a6d3 https://campbell-muscle-lab.github.io/howtos_Python/pages/documentation/best_practices/vscode_docstring_extension/vscode_docstring_extension.html#:~:text=Type%2C%20Ctrl%20%2B%20Shift%20%2B%20P,select%20the%20NumPy%20docstring%20format. VS Code
202 e2b5e914-ffe1-44d2-8e92-58f8c5d92bb2 https://superuser.com/questions/1386061/how-to-suppress-some-python-errors-warnings-in-vs-code VS Code
203 9439a27b-18ae-42d8-9778-5f68f891805e https://stackoverflow.com/questions/75832474/how-to-keep-cursor-in-debug-console-when-debugging-in-visual-studio-code VS Code
204 ae506c68-352c-4094-9caa-ee9d42052317 https://superuser.com/questions/1460404/get-visual-studio-code-terminal-history?rq=1 VS Code
205 ea98c5d7-3cf9-4f9b-8ad3-366b58e0fcae https://superuser.com/questions/1748097/vs-code-disable-tree-view-find-explorer-search VS Code
206 c714dcee-cad3-4e12-8f3c-12bdcfcdb048 https://superuser.com/questions/1417361/how-to-disable-file-filtering-in-vs-code-sidebar-explorer?rq=1 VS Code
207 930fdb3b-11a8-46fe-9bac-577332e2640e https://superuser.com/questions/1270103/how-to-switch-the-cursor-between-terminal-and-code-in-vscode VS Code
208 276cc624-87ea-4f08-ab93-f770e3790175 https://www.quora.com/unanswered/How-do-you-set-the-line-length-in-Visual-Studio-Code VS Code
209 9d425400-e9b2-4424-9a4b-d4c7abac4140 https://superuser.com/questions/1466771/is-there-a-way-to-make-editor-tabs-stack-in-vs-code VS Code
210 7a4deb26-d57d-4ea9-9a73-630f66a7b568 https://www.quora.com/How-do-I-edit-a-photo-in-GIMP GIMP
211 554785e9-4523-4e7a-b8e1-8016f565f56a https://www.quora.com/How-do-I-edit-a-photo-in-GIMP GIMP
212 77b8ab4d-994f-43ac-8930-8ca087d7c4b4 https://superuser.com/questions/1636113/how-to-get-gimp-to-recognize-images-or-pictures-folder-as-the-default-folder-for GIMP
213 f4aec372-4fb0-4df5-a52b-79e0e2a5d6ce https://superuser.com/questions/612338/how-do-i-select-and-move-an-object-in-gimp GIMP
214 d52d6308-ec58-42b7-a2c9-de80e4837b2b https://superuser.com/questions/1447106/how-to-get-rid-of-the-gimp-tool-options-box GIMP
215 2a729ded-3296-423d-aec4-7dd55ed5fbb3 https://www.youtube.com/watch?v=lOzSiOIipSM GIMP
216 b148e375-fe0b-4bec-90e7-38632b0d73c2 https://www.quora.com/How-do-I-add-layers-in-GIMP GIMP
217 a746add2-cab0-4740-ac36-c3769d9bfb46 https://www.youtube.com/watch?v=_L_MMU22bAw GIMP
218 7b7617bd-57cc-468e-9c91-40c4ec2bcb3d https://www.youtube.com/watch?v=G_PjQAy0iiU GIMP
219 d16c99dc-2a1e-46f2-b350-d97c86c85c15 https://stackoverflow.com/questions/75185543/use-gimp-to-resize-image-in-one-layer-only GIMP
220 573f79b5-abfe-4507-b455-251d45fe6198 https://stackoverflow.com/questions/45196895/gimp-add-padding-to-multiple-images GIMP
221 06ca5602-62ca-47f6-ad4f-da151cde54cc https://stackoverflow.com/questions/74664666/how-to-export-palette-based-png-in-gimp GIMP
222 fa9b1e10-4d2d-4a13-af76-7efa822b6a8b https://stackoverflow.com/questions/24626608/how-to-combine-several-png-images-as-layers-in-a-single-xcf-image GIMP
223 6b2b72ed-3a10-4849-876a-750f7cdf3886 https://stackoverflow.com/questions/21018007/resize-image-to-fit-canvas-gimp GIMP
224 d0e42fd2-d290-46b3-b598-a6e2b7be9c85 https://stackoverflow.com/questions/56758689/stop-gimp-from-merging-layers-when-de-selecting GIMP
225 e2dd0213-26db-4349-abe5-d5667bfd725c https://superuser.com/questions/839650/how-to-move-an-inserted-text-box-in-gimp GIMP
226 f723c744-e62c-4ae6-98d1-750d3cd7d79d https://www.reddit.com/r/GIMP/comments/12e57w8/how_to_use_gimp_to_exaggerate_contrast/ GIMP
227 8d6b1c9c-1aab-47fe-9ba5-e84c838d0c57 https://www.quora.com/How-can-email-attachments-be-converted-into-a-word-document-using-Mozilla-Thunderbird multiple
228 11e1e614-9696-4d94-88c9-8e556880d41a https://ifttt.com/applets/L2A89geP-send-chrome-software-update-release-alerts-to-email multiple
229 57956154-f0fe-486b-88b8-e7126da035a9 https://zapier.com/apps/email/integrations/google-sheets/547/get-email-notifications-for-new-rows-in-a-google-sheets-spreadsheet multiple
230 ec14c524-b245-456d-abd6-ec12c746e9f8 https://zapier.com/apps/gmail/integrations/google-sheets/2618/save-new-gmail-emails-matching-certain-traits-to-a-google-spreadsheet multiple
231 cbf5fbda-425e-4619-bcf2-0ea8d4c0bfa3 https://zapier.com/apps/google-sheets/integrations/google-slides/13919/refresh-charts-on-a-google-slides-presentation-when-rows-are-updated-on-google-sheets multiple
232 a54284d0-7b93-4327-bfcc-3a421516dbdd https://superuser.com/questions/655622/cannot-drag-images-from-thunderbird-to-word multiple
233 58565672-7bfe-48ab-b828-db349231de6b https://superuser.com/questions/1792660/open-link-from-other-application-does-not-open-the-url-in-firefox multiple
234 6d72aad6-187a-4392-a4c4-ed87269c51cf https://superuser.com/questions/923171/converting-openoffice-impress-presentation-to-video-without-screen-recording multiple
235 937087b6-f668-4ba6-9110-60682ee33441 https://superuser.com/questions/187440/set-default-ubuntu-video-player-as-vlc multiple
236 f8cfa149-d1c1-4215-8dac-4a0932bad3c2 https://superuser.com/questions/1803088/libreoffice-calc-clears-clipboard multiple
237 5e974913-6905-4c3f-8b65-d7837f3931cc https://stackoverflow.com/questions/61856141/how-can-i-start-thunderbird-and-minimize-the-window-on-startup-in-ubuntu multiple
238 7c179dad-f1c7-4892-b53f-d1c4023d23c7 https://stackoverflow.com/questions/21155085/pasting-excel-tables-in-thunderbird-e-mail-client multiple
239 4a68b2dd-70f2-4532-9bc1-d21878bd8cb2 https://stackoverflow.com/questions/65669955/thunderbird-how-to-send-a-mail-to-all-receivers-of-a-folder multiple
240 c8457fde-b14b-4aba-b402-144842ea29e1 https://stackoverflow.com/questions/65788200/how-to-open-xlsx-files-in-ms-excel-from-vs-code multiple
241 81c425f5-78f3-4771-afd6-3d2973825947 https://www.zyxware.com/articles/3770/how-to-transfer-data-in-libreoffice-calc-to-libreoffice-writer-in-table-format multiple
242 bb83cab4-e5c7-42c7-a67b-e46068032b86 https://ask.libreoffice.org/t/save-impress-presentation-as-writer-document/5291/4 multiple
243 227d2f97-562b-4ccb-ae47-a5ec9e142fbb https://discourse.gnome.org/t/gimp-and-libre-office-writer/15430/4 multiple
244 a6bbc08c-51e9-4ee4-9327-83d05075d960 https://forum.openoffice.org/en/forum/viewtopic.php?t=105055 multiple
245 964e6e03-ba31-466b-8c15-5a351a81f675 https://www.maketecheasier.com/mail-merge-thunderbird-calc/ multiple
246 2fe4b718-3bd7-46ec-bdce-b184f5653624 https://www.thewindowsclub.com/how-to-create-animated-gif-from-a-video-file-using-vlc-and-gimp multiple
247 d02b9364-6bb0-4c7e-9dbd-4db62822bc26 https://stackoverflow.com/questions/38306910/simple-python-script-to-get-a-libreoffice-base-field-and-play-on-vlc multiple
248 57fb469b-127a-46fa-8281-bbb3840efdf5 https://support.mozilla.org/en-US/questions/1150626 multiple
249 3680a5ee-6870-426a-a997-eba929a0d25c https://unix.stackexchange.com/questions/510850/how-to-open-calc-from-terminal-and-insert-files multiple
250 2d8c8a20-6f54-4c2e-ad56-61fbe7af6b78 https://www.quora.com/How-do-I-force-LibreOffice-Calc-to-recalculate-a-spreadsheet-from-the-command-line multiple
251 ee9a3c83-f437-4879-8918-be5efbb9fac7 https://stackoverflow.com/questions/64589140/convert-ods-to-csv-using-command-line-when-libreoffice-instance-is-running multiple
252 f7dfbef3-7697-431c-883a-db8583a4e4f9 https://www.thegeekdiary.com/libreoffice-command-examples-in-linux/ multiple
253 2b9493d7-49b8-493a-a71b-56cd1f4d6908 https://devicetests.com/kill-libreoffice-writer-command-line-ubuntu multiple
254 51f5801c-18b3-4f25-b0c3-02f85507a078 https://github.com/danielrcollins1/ImpressExtractNotes multiple
255 81de345e-5473-4cb6-a74d-b6abf3475a6a https://stackoverflow.com/questions/45588952/how-can-i-compose-and-send-email-in-thunderbird-from-commandline multiple
256 2c9fc0de-3ee7-45e1-a5df-c86206ad78b5 https://nikki-ricks.medium.com/how-to-use-git-add-commit-and-push-in-vs-code-and-command-line-35c0e8c47b62 multiple
257 510f64c8-9bcc-4be1-8d30-638705850618 https://www.geeksforgeeks.org/how-to-start-vs-code-from-the-terminal-command-line/ multiple
258 9ff484f7-5c09-4398-ae29-d5904e59e138 https://stackoverflow.com/questions/38606973/playing-opening-and-pausing-vlc-command-line-executed-from-python-scripts multiple
259 d9b7c649-c975-4f53-88f5-940b29c47247 https://marketplace.uipath.com/listings/extract-the-first-1000-gmail-emails-from-the-current-month-in-a-new-google-sheets-report multiple
260 be4ef0dc-0f70-4936-81d8-3cd2b04482f8 https://marketplace.uipath.com/listings/table-data-extraction-for-sales-opportunities-to-excel-workbook multiple
261 78aed49a-a710-4321-a793-b611a7c5b56b https://marketplace.uipath.com/listings/upload-email-attachments-from-gmail-to-google-drive multiple
262 897e3b53-5d4d-444b-85cb-2cdc8a97d903 https://marketplace.uipath.com/listings/convert-word-file-to-pdf-and-store-in-onedrive multiple
263 4e9f0faf-2ecc-4ae8-a804-28c9a75d1ddc https://marketplace.uipath.com/listings/extract-data-from-a-new-invoice-file-in-google-drive-and-store-it-in-google-sheets4473 multiple
264 b52b40a5-ad70-4c53-b5b0-5650a8387052 https://marketplace.uipath.com/listings/merge-pdfs-from-gmail-email-attachments-and-upload-to-gogle-drive multiple
265 46407397-a7d5-4c6b-92c6-dbe038b1457b https://marketplace.uipath.com/listings/upload-to-google-drive-images-from-pdf-attachments-received-via-gmail multiple
266 a0b9dc9c-fc07-4a88-8c5d-5e3ecad91bcb https://marketplace.uipath.com/listings/backup-important-emails-to-onedrive-or-sharepoint multiple
267 665f4af1-617d-4009-baff-84ff66071e6a https://www.howtogeek.com/663927/how-to-open-google-chrome-using-command-prompt-on-windows-10/#open-chrome-straight-to-a-specific-website multiple
268 e6313b30-3903-4ed9-8c7d-4c47bf51fc96 https://stackoverflow.com/questions/12258086/how-do-i-run-google-chrome-as-root multiple

View File

@@ -1,238 +0,0 @@
import csv
import os
import yt_dlp
from docx import Document
import requests
from bs4 import BeautifulSoup
from PIL import Image
import pytesseract
from io import BytesIO
from docx import Document
import re
import markdownify
from markdownify import markdownify as md
def valid_xml_char_ordinal(c):
codepoint = ord(c)
# conditions ordered by presumed frequency
return (
0x20 <= codepoint <= 0xD7FF or
codepoint in (0x9, 0xA, 0xD) or
0xE000 <= codepoint <= 0xFFFD or
0x10000 <= codepoint <= 0x10FFFF
)
def download_and_clean_youtube_subtitles(video_url, txt_filepath):
# set up youtube-dl options to download the subtitles
subtitles_path = txt_filepath[0:-4]
ydl_opts = {
'skip_download': True,
'writesubtitles': True,
'writeautomaticsub': True, # if no subtitles are available, try to generate them
'subtitleslangs': ['en'],
'outtmpl': f'{subtitles_path}.%(ext)s',
'quiet': True,
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
# download the subtitles
ydl.download([video_url])
subtitle_file = f'{subtitles_path}.en.vtt'
# read the subtitle file
subtitles = []
try:
with open(subtitle_file, 'r', encoding='utf-8') as file:
lines = file.readlines()
# define a pattern to match the time line
pattern = re.compile(r'(\d{2}:\d{2}:\d{2}.\d{3} --> \d{2}:\d{2}:\d{2}.\d{3})|(^WEBVTT)|(^Kind: captions)|(^Language: .*)')
# clean the subtitles
for line in lines:
# if this line is a time line or it is blank , skip it
if pattern.match(line) or line.strip() == '':
continue
# add this subtitle line to subtitles list, remove the trailing spaces and line change
subtitles.append(line.strip())
# remove duplicated subtitles
subtitles = list(dict.fromkeys(subtitles))
# save the subtitles as a txt file
with open(txt_filepath, 'w', encoding='utf-8') as f:
for line in subtitles:
if line:
f.write(line + '\n')
except IOError:
print(f"Could not read file: {subtitle_file}")
# scrape a webpage and perform OCR on images
def scrape_and_ocr_forum(url, doc):
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
text_elements = soup.find_all(['h1', 'h2', 'h3', 'p', 'li'])
for element in text_elements:
doc.add_paragraph(element.get_text())
image_elements = soup.find_all('img')
for image in image_elements:
if 'src' not in image.attrs:
continue
image_url = image['src']
if image_url.startswith('http'):
if not image_url.endswith('.svg') and not image_url.endswith('.png'):
continue
if 'neveragain.allstatics.com/2019/assets/icon/logo' in image_url:
continue
img_response = requests.get(image_url, stream=True)
img = Image.open(BytesIO(img_response.content))
ocr_text = pytesseract.image_to_string(img)
if ocr_text != ' ' and ocr_text != '':
cleaned_string = ''.join(c for c in ocr_text if valid_xml_char_ordinal(c))
doc.add_paragraph(cleaned_string)
def superuser_to_markdown(url, doc_filepath):
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
# set up the markdown document
markdown_content = ""
# get the question title and body
question_title = soup.find('h1').get_text(strip=True)
question = soup.find('div', {'id': 'question'})
if question:
question_body = question.find('div', {'class': 's-prose js-post-body'}).prettify()
markdown_content += f"# {question_title}\n\n" + markdownify.markdownify(question_body, heading_style="ATX") + "\n\n"
# get all answers
answers = soup.find_all('div', {'class': 'answer'})
for answer in answers:
answer_body = answer.find('div', {'class': 's-prose js-post-body'}).prettify()
markdown_content += markdownify.markdownify(answer_body, heading_style="ATX") + "\n\n"
# deal with images and perform OCR
all_img_tags = question.find_all('img') + [img for answer in answers for img in answer.find_all('img')]
for img_tag in all_img_tags:
image_src = img_tag.get('src') or img_tag.get('data-src') # Superuser uses lazy loading
if image_src and image_src.startswith('http'):
img_response = requests.get(image_src, stream=True)
img = Image.open(BytesIO(img_response.content))
ocr_text = pytesseract.image_to_string(img)
if ocr_text.strip(): # if the OCR result is not empty, add it to the markdown content
markdown_content += "```\n" + ocr_text.strip() + "\n```\n\n"
with open(doc_filepath, 'w', encoding='utf-8') as f:
f.write(markdown_content)
def stack_overflow_to_markdown(url, doc_filepath):
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
# set up the markdown document
markdown_content = ""
# get the question title and body
question = soup.find('div', {'id': 'question'})
question_title = soup.find('h1').get_text(strip=True)
if question:
question_body = question.find('div', {'class': 's-prose js-post-body'}).prettify()
markdown_content += f"# {question_title}\n\n" + markdownify.markdownify(question_body, heading_style="ATX") + "\n\n"
# get all answers
answers = soup.find_all('div', {'class': 'answer'})
for answer in answers:
answer_body = answer.find('div', {'class': 's-prose js-post-body'}).prettify()
markdown_content += markdownify.markdownify(answer_body, heading_style="ATX") + "\n\n"
# deal with images and perform OCR
all_img_tags = soup.find_all('img')
for img_tag in all_img_tags:
image_url = img_tag['src']
if image_url.startswith('http') and (image_url.endswith('.svg') or image_url.endswith('.png')): # 确保图片URL有效
img_response = requests.get(image_url, stream=True)
img = Image.open(BytesIO(img_response.content))
ocr_text = pytesseract.image_to_string(img)
if ocr_text.strip():
markdown_content += "```\n" + ocr_text.strip() + "\n```\n\n"
with open(doc_filepath, 'w', encoding='utf-8') as f:
f.write(markdown_content)
def scrape_webpage_to_markdown(url, doc_filepath):
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
articles = soup.find_all('article') or soup.find_all('main') or soup.find_all('div', {'class': 'lia-message-body-content'})
if not articles:
return
markdown_content = ''
# scrape the webpage and perform OCR on images
for article in articles:
for child in article.recursiveChildGenerator():
# if this is an image, perform OCR
if child.name == 'img':
img_url = child.get('src')
if not img_url.startswith(('http:', 'https:')):
img_url = '{}{}'.format(url, img_url)
if not img_url.endswith('.svg') and not img_url.endswith('.png'):
continue
if 'neveragain.allstatics.com/2019/assets/icon/logo' in img_url:
continue
try:
img_response = requests.get(img_url, stream=True)
img = Image.open(BytesIO(img_response.content))
ocr_text = pytesseract.image_to_string(img)
if ocr_text.strip():
markdown_content += '\n```plaintext\n{}\n```\n'.format(ocr_text.strip())
continue
except PIL.UnidentifiedImageError:
print("unidentified image")
# Not an image, so continue recursively calling function
if child.name is None:
continue
html_str = str(child)
markdown_content += md(html_str) + '\n\n'
with open(doc_filepath, 'w', encoding='utf-8') as f:
f.write(markdown_content)
# process a URL and save the file
def process_url(url, doc_id, app):
doc_filepath = f"/content/drive/MyDrive/SourceDoc/{doc_id}_{app}.md"
txt_filepath = f"/content/drive/MyDrive/SourceDoc/{doc_id}_{app}.txt"
doc = Document()
if 'youtube.com' in url or 'youtu.be' in url:
download_and_clean_youtube_subtitles(url, txt_filepath)
elif 'superuser.com' in url:
superuser_to_markdown(url, doc_filepath)
elif 'stackoverflow.com' in url:
stack_overflow_to_markdown(url, doc_filepath)
else:
scrape_webpage_to_markdown(url, doc_filepath)
# read the CSV file and process each URL
csv_filepath = './Get_Source_Doc - Sheet1.csv'
with open(csv_filepath, 'r', newline='', encoding='utf-8') as csvfile:
reader = csv.DictReader(csvfile)
cnt = 55
for row in reader:
if cnt>0:
cnt -= 1
continue
process_url(row['Source'], row['id'], row['InvolvedApp'])
print(row)

View File

@@ -1,293 +0,0 @@
import csv
import os
import io
import fitz
import yt_dlp
from docx import Document
import requests
from bs4 import BeautifulSoup
from PIL import Image
import pytesseract
from io import BytesIO
from docx import Document
import re
import markdownify
from markdownify import markdownify as md
def download_pdf(url):
response = requests.get(url)
response.raise_for_status() # 确保请求是成功的
return io.BytesIO(response.content)
def pdf_to_markdown(pdf_stream, markdown_path):
document = fitz.open(stream=pdf_stream, filetype="pdf")
markdown_content = ""
for page_number in range(len(document)):
page = document[page_number]
text = page.get_text()
markdown_content += text + "\n\n"
# 提取图片并添加到 Markdown 文件。图片被保存在同一目录下
image_list = page.get_images(full=True)
if image_list:
markdown_content += f"### Page {page_number + 1} Images\n"
for img_index, image in enumerate(image_list, start=1):
# 提取图片
xref = image[0]
base64_image = document.extract_image(xref)
image_bytes = base64_image["image"]
# 写入图片到磁盘
image_filename = f"output_image_page_{page_number + 1}_{img_index}.png"
image_abs_path = os.path.join(os.path.dirname(markdown_path), image_filename)
with open(image_abs_path, "wb") as image_file:
image_file.write(image_bytes)
# 在 Markdown 文件中添加图片引用
markdown_content += f"![Page {page_number + 1} Image {img_index}]({image_filename})\n\n"
with open(markdown_path, "w", encoding="utf-8") as md_file:
md_file.write(markdown_content)
document.close()
def valid_xml_char_ordinal(c):
codepoint = ord(c)
# conditions ordered by presumed frequency
return (
0x20 <= codepoint <= 0xD7FF or
codepoint in (0x9, 0xA, 0xD) or
0xE000 <= codepoint <= 0xFFFD or
0x10000 <= codepoint <= 0x10FFFF
)
def download_and_clean_youtube_subtitles(video_url, txt_filepath):
# 设置yt-dlp库的选项来下载字幕
subtitles_path = txt_filepath[0:-4]
ydl_opts = {
'skip_download': True,
'writesubtitles': True,
'writeautomaticsub': True, # 如果视频没有字幕,尝试下载自动生成的字幕
'subtitleslangs': ['en'], # 下载英文字幕
'outtmpl': f'{subtitles_path}.%(ext)s', # 确保保存到可写目录
'quiet': True,
}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
# 获取视频信息,下载字幕文件
ydl.download([video_url])
subtitle_file = f'{subtitles_path}.en.vtt'
# 读取下载的字幕文件
subtitles = []
try:
with open(subtitle_file, 'r', encoding='utf-8') as file:
# 读取所有行
lines = file.readlines()
# 正则表达式匹配时间戳和其他不相关的标记
pattern = re.compile(r'(\d+:\d\d:\d\d.\d+ --> \d+:\d\d:\d\d.\d+)|(\s*<[^>]+>)')
# 去除时间戳和HTML标签等只保留字幕文本
lines = [re.sub(pattern, '', line).strip() for line in lines if line.strip() and not pattern.match(line)]
# 清洗字幕
for line in lines:
# 如果这是一个时间线或者其他不需要的信息,跳过它
if pattern.match(line) or line.strip() == '':
continue
# 添加到字幕列表,同时去除愈加和前导空白符
subtitles.append(line.strip())
# 去除可能的重复行
subtitles = list(dict.fromkeys(subtitles))
# 保存至txt文件
with open(txt_filepath, 'w', encoding='utf-8') as f:
for line in subtitles:
if line: # 避免写入空行
f.write(line + '\n')
except IOError:
print(f"Could not read file: {subtitle_file}")
# 爬取论坛内容对图片进行OCR处理并保存为.docx文件
def scrape_and_ocr_forum(url, doc):
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
text_elements = soup.find_all(['h1', 'h2', 'h3', 'p', 'li'])
for element in text_elements:
doc.add_paragraph(element.get_text())
image_elements = soup.find_all('img')
for image in image_elements:
if 'src' not in image.attrs:
continue
image_url = image['src']
if image_url.startswith('http'):
if not image_url.endswith('.svg') and not image_url.endswith('.png'):
continue
if 'neveragain.allstatics.com/2019/assets/icon/logo' in image_url:
continue
img_response = requests.get(image_url, stream=True)
img = Image.open(BytesIO(img_response.content))
ocr_text = pytesseract.image_to_string(img)
if ocr_text != ' ' and ocr_text != '':
cleaned_string = ''.join(c for c in ocr_text if valid_xml_char_ordinal(c))
doc.add_paragraph(cleaned_string)
def superuser_to_markdown(url, doc_filepath):
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
# 创建Markdown文件的内容
markdown_content = ""
# 获取问题标题和内容
question_title = soup.find('h1').get_text(strip=True)
question = soup.find('div', {'id': 'question'})
if question:
question_body = question.find('div', {'class': 's-prose js-post-body'}).prettify()
markdown_content += f"# {question_title}\n\n" + markdownify.markdownify(question_body, heading_style="ATX") + "\n\n"
# 获取所有回答
answers = soup.find_all('div', {'class': 'answer'})
for answer in answers:
answer_body = answer.find('div', {'class': 's-prose js-post-body'}).prettify()
markdown_content += markdownify.markdownify(answer_body, heading_style="ATX") + "\n\n"
# 处理图片并执行OCR
all_img_tags = question.find_all('img') + [img for answer in answers for img in answer.find_all('img')]
for img_tag in all_img_tags:
image_src = img_tag.get('src') or img_tag.get('data-src') # Superuser使用延迟加载的图片
if image_src and image_src.startswith('http'):
img_response = requests.get(image_src, stream=True)
img = Image.open(BytesIO(img_response.content))
ocr_text = pytesseract.image_to_string(img)
if ocr_text.strip(): # 如果OCR结果非空则添加到Markdown内容中
markdown_content += "```\n" + ocr_text.strip() + "\n```\n\n"
# 将Markdown内容写入文件
with open(doc_filepath, 'w', encoding='utf-8') as f:
f.write(markdown_content)
def stack_overflow_to_markdown(url, doc_filepath):
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
# 创建Markdown文件的内容
markdown_content = ""
# 获取问题标题和内容
question = soup.find('div', {'id': 'question'})
question_title = soup.find('h1').get_text(strip=True)
if question:
question_body = question.find('div', {'class': 's-prose js-post-body'}).prettify()
markdown_content += f"# {question_title}\n\n" + markdownify.markdownify(question_body, heading_style="ATX") + "\n\n"
# 获取所有回答
answers = soup.find_all('div', {'class': 'answer'})
for answer in answers:
answer_body = answer.find('div', {'class': 's-prose js-post-body'}).prettify()
markdown_content += markdownify.markdownify(answer_body, heading_style="ATX") + "\n\n"
# 处理图片并执行OCR
all_img_tags = soup.find_all('img')
for img_tag in all_img_tags:
image_url = img_tag['src']
if image_url.startswith('http') and (image_url.endswith('.svg') or image_url.endswith('.png')): # 确保图片URL有效
img_response = requests.get(image_url, stream=True)
img = Image.open(BytesIO(img_response.content))
ocr_text = pytesseract.image_to_string(img)
if ocr_text.strip(): # 如果OCR结果非空则添加到Markdown内容中
markdown_content += "```\n" + ocr_text.strip() + "\n```\n\n"
# 将Markdown内容写入文件
with open(doc_filepath, 'w', encoding='utf-8') as f:
f.write(markdown_content)
def scrape_webpage_to_markdown(url, doc_filepath):
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
# 假设文章内容在 HTML 的 'article' 标签中,根据实际页面结构调整
articles = soup.find_all('article') or soup.find_all('main') or soup.find_all('div', {'id':'steps'}, {'class':'section_text'}) # 或其他包含主要内容的HTML标签
if not articles:
articles = soup.find_all('div', {'class': 'lia-message-body-content'})
markdown_content = ''
# 抓取所有图文信息
for article in articles:
for child in article.recursiveChildGenerator():
# 如果是图片则进行OCR
if child.name == 'img':
img_url = child.get('src')
if not img_url:
continue
if not img_url.startswith(('http:', 'https:')):
img_url = '{}{}'.format(url, img_url)
if not img_url.endswith('.svg') and not img_url.endswith('.png'):
continue
if 'neveragain.allstatics.com/2019/assets/icon/logo' in img_url:
continue
print(img_url)
try:
img_response = requests.get(img_url, stream=True)
img = Image.open(BytesIO(img_response.content))
ocr_text = pytesseract.image_to_string(img)
if ocr_text.strip():
markdown_content += '\n```plaintext\n{}\n```\n'.format(ocr_text.strip())
continue
except PIL.UnidentifiedImageError:
print("unidentified image")
# 不是标签可能是NavigableString或其他
if child.name is None:
continue
# 抓取标签并转换为Markdown
html_str = str(child)
markdown_content += md(html_str) + '\n\n'
# 写入markdown文件
with open(doc_filepath, 'w', encoding='utf-8') as f:
f.write(markdown_content)
# 处理单个URL
def process_url(url, doc_id, app):
doc_filepath = f"/content/drive/MyDrive/SourceDoc/{doc_id}_{app}.md"
txt_filepath = f"/content/drive/MyDrive/SourceDoc/{doc_id}_{app}.txt"
doc = Document()
if 'youtube.com' in url or 'youtu.be' in url:
download_and_clean_youtube_subtitles(url, txt_filepath)
elif url.endswith('.pdf'):
pdf_stream = download_pdf(url)
pdf_to_markdown(pdf_stream, doc_filepath)
elif 'superuser.com' in url or 'askubuntu.com' in url:
superuser_to_markdown(url, doc_filepath)
elif 'stackoverflow.com' in url:
stack_overflow_to_markdown(url, doc_filepath)
else:
scrape_webpage_to_markdown(url, doc_filepath)
# 读取CSV文件中的数据并执行对应操作
csv_filepath = '/content/Get_Source_Doc - Sheet1.csv' # 更新为你的CSV文件实际路径
with open(csv_filepath, 'r', newline='', encoding='utf-8') as csvfile:
reader = csv.DictReader(csvfile)
cnt = 176
for row in reader:
if cnt>0:
cnt -= 1
continue
process_url(row['Source'], row['id'], row['InvolvedApp'])
print(row)

View File

@@ -1,17 +0,0 @@
import requests
import json
url = "https://tiktok-download-video1.p.rapidapi.com/feedSearch"
querystring = {"keywords":"Ubuntu desktop","count":"10","cursor":"0","region":"US","publish_time":"0","sort_type":"1"}
headers = {
"X-RapidAPI-Key": "YOUR_API_KEY",
"X-RapidAPI-Host": "tiktok-download-video1.p.rapidapi.com"
}
response = requests.get(url, headers=headers, params=querystring)
print(response.json())
with open("./record_1.json", "w") as f:
json.dump(response.json(), f)

View File

@@ -1,506 +0,0 @@
{
"code": 0,
"msg": "success",
"processed_time": 1.2181,
"data": {
"videos": [
{
"aweme_id": "v0f025gc0000cfdgkhbc77ufdvjieor0",
"video_id": "7195358717750070555",
"region": "ID",
"title": "#CapCut comparing the pros and cons of two operating system: #windows and #linux. \ud83d\udc68\u200d\ud83d\udcbb Unleash the power of #Linux ! Experience a better, more efficient computing with this open-source OS #windows11 #windows10 #ubuntu #programming #computerscience #opensource #technology #FaktaProgrammer #jagocoding #debian #malware #hacking #ProgrammerImut ",
"cover": "https://p16-sign-useast2a.tiktokcdn.com/tos-useast2a-p-0037-aiso/2410a4ce346f409ca1f0ba4b543ce7c3_1675300016~tplv-dmt-logom:tos-useast2a-pv-0037-aiso/77578d1e8c9a4efaaa057636e35ab262.image?x-expires=1703127600&x-signature=9GzGgxD2brvooV5TsmsuyCFqloU%3D&s=SEARCH&se=false&sh=&sc=dynamic_cover&l=2023122003034909B448A5A66536000675",
"origin_cover": "https://p16-sign-useast2a.tiktokcdn.com/tos-useast2a-p-0037-aiso/5904cc8b48a14d4081f05e97dd006328_1675300016~tplv-tiktokx-360p.webp?x-expires=1703127600&x-signature=zKuLeet7txU5bAPZCeWTFRYHZas%3D&s=SEARCH&se=false&sh=&sc=feed_cover&l=2023122003034909B448A5A66536000675",
"duration": 15,
"play": "https://v16m.tiktokcdn-us.com/385a61f83cddd3276b6b3955ae059c98/6582ae05/video/tos/useast2a/tos-useast2a-pve-0037-aiso/oIYbF3pDWeVcQUPIUB8jADjnQ2PNBDBLbw2Cne/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1398&bt=699&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=NDQzaDw1N2Q8OTpoNzNkPEBpM3JseWk6ZmtqaTMzZjgzM0BjM2EtXzRgNjIxXjJeNWJhYSNvYmdhcjRfaGFgLS1kL2Nzcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00008000",
"wmplay": "https://v16m.tiktokcdn-us.com/f6c45472d69d11105cd4f55ebb8e7d13/6582ae05/video/tos/useast2a/tos-useast2a-pve-0037c001-aiso/oI2pBQPnjBbeYpn8DBFwAAI3CDcbb27QeVUKnF/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1124&bt=562&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=11&rc=ZzVlODZnZWdmM2k2OzhmZ0BpM3JseWk6ZmtqaTMzZjgzM0AtX2BfLjJgX2AxYGJjNDQzYSNvYmdhcjRfaGFgLS1kL2Nzcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00008000",
"music": "https://sf16-ies-music.tiktokcdn.com/obj/ies-music-aiso/7195358744048438042.mp3",
"music_info": {
"id": "7195358701144902426",
"title": "original sound - ferryops_",
"play": "https://sf16-ies-music.tiktokcdn.com/obj/ies-music-aiso/7195358744048438042.mp3",
"cover": "https://p16-sign-useast2a.tiktokcdn.com/tos-useast2a-avt-0068-giso/5b59cd09101f0a570efe722f5f1f64b1~c5_1080x1080.jpeg?x-expires=1703127600&x-signature=w1qF6e5dmhhCgWPwiDvSJxZVQO4%3D",
"author": "ferry",
"original": true,
"duration": 15,
"album": ""
},
"play_count": 814198,
"digg_count": 55739,
"comment_count": 1876,
"share_count": 1911,
"download_count": 62,
"create_time": 1675300014,
"anchors": null,
"anchors_extras": "",
"is_ad": false,
"commerce_info": {
"adv_promotable": false,
"auction_ad_invited": false,
"branded_content_type": 0,
"with_comment_filter_words": false
},
"commercial_video_info": "",
"author": {
"id": "6924124906000303105",
"unique_id": "ferryops_",
"nickname": "ferry",
"avatar": "https://p16-sign-useast2a.tiktokcdn.com/tos-useast2a-avt-0068-giso/5b59cd09101f0a570efe722f5f1f64b1~c5_300x300.jpeg?x-expires=1703127600&x-signature=NNsWWsjrvJ206kY8qyFxiZE5k20%3D"
},
"is_top": 0
},
{
"aweme_id": "v12044gd0000chd8vdbc77uea2obsidg",
"video_id": "7231251211062758702",
"region": "US",
"title": "What would you do with this data? #computervision #machinelearning #objectdetection #csproject #softwareengineer ",
"cover": "https://p16-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/ee1c7307d8ba46008d9a2f1002ffe99b_1683656884~tplv-dmt-logom:tos-useast5-i-0068-tx/5f1c1ee9247449aa8d86b36beeacc10d.image?x-expires=1703127600&x-signature=Wzp2xq0jwoPBoDrSEDydKP8RitY%3D&s=SEARCH&se=false&sh=&sc=dynamic_cover&l=2023122003034909B448A5A66536000675",
"origin_cover": "https://p19-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/c5b40f31562a4b1c9bc6e9066b8da76c_1683656884~tplv-tiktokx-360p.webp?x-expires=1703127600&x-signature=q301jDESVmXKzurDSZEsAaY71u4%3D&s=SEARCH&se=false&sh=&sc=feed_cover&l=2023122003034909B448A5A66536000675",
"duration": 126,
"play": "https://v16m.tiktokcdn-us.com/dd179aa8851feda2a2f84f6859d626ce/6582ae74/video/tos/useast5/tos-useast5-pve-0068-tx/osaeSLPIjUoAMkRbctIQWFnIjDbeDH1RCACekt/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1490&bt=745&bti=NTY6QGo0QHM6OjZANDQuYCMucCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=Ojg4OGZnPDtlZzY0Ojk6NEBpamxlNWg6Zmc7azMzZzczNEAxYV5iMy1gXjIxY2ExNi0yYSNhcGxecjRfc2FgLS1kMS9zcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00010000",
"wmplay": "https://v16m.tiktokcdn-us.com/b36feb41d71b86ed6e4e6ea7a72e71af/6582ae74/video/tos/useast5/tos-useast5-pve-0068-tx/oAIHCeIo1WkngCFIA1kYRbDfeIAjagtcq28oYQ/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1548&bt=774&bti=NTY6QGo0QHM6OjZANDQuYCMucCMxNDNg&cs=0&ds=3&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=Mzw7Zjs2NjY1MzxpNTc3aEBpamxlNWg6Zmc7azMzZzczNEBfYy40XzBeNWAxMWEuNF5gYSNhcGxecjRfc2FgLS1kMS9zcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00010000",
"music": "https://sf19.tiktokcdn-us.com/obj/ies-music-tx/7231251227135281962.mp3",
"music_info": {
"id": "7231251193572477742",
"title": "original sound - codingai",
"play": "https://sf19.tiktokcdn-us.com/obj/ies-music-tx/7231251227135281962.mp3",
"cover": "https://p16-sign.tiktokcdn-us.com/tos-useast8-avt-0068-tx2/69600b7c44fc05a3b118277409c5c6fb~c5_1080x1080.jpeg?x-expires=1703127600&x-signature=eg%2B7HmT9JXLgeMm0WgzQHu9nh2I%3D",
"author": "Eric",
"original": true,
"duration": 126,
"album": ""
},
"play_count": 277284,
"digg_count": 20971,
"comment_count": 188,
"share_count": 868,
"download_count": 221,
"create_time": 1683656883,
"anchors": null,
"anchors_extras": "",
"is_ad": false,
"commerce_info": {
"adv_promotable": false,
"auction_ad_invited": false,
"branded_content_type": 0,
"with_comment_filter_words": false
},
"commercial_video_info": "",
"author": {
"id": "7044388915265668143",
"unique_id": "codingai",
"nickname": "Eric",
"avatar": "https://p16-sign.tiktokcdn-us.com/tos-useast8-avt-0068-tx2/69600b7c44fc05a3b118277409c5c6fb~c5_300x300.jpeg?x-expires=1703127600&x-signature=2n0cRjUEqT4lHZcZkoKH0wSURVY%3D"
},
"is_top": 0
},
{
"aweme_id": "v09044g40000c9g4kbrc77u91sdh9jp0",
"video_id": "7088747410289102086",
"region": "IT",
"title": "Come installare #ubuntu e far risorgere un vecchio pc #aletech",
"cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-p-0068/e25e3d586f4c4cdfa18235f07b686497_1650477660~tplv-dmt-logom:tos-useast2a-v-0068/85ca53bad5e4402ba107aa9f88f3d396.image?x-expires=1703127600&x-signature=D18vkekhPxb8FvOOLcFLUcx9xLA%3D&s=SEARCH&se=false&sh=&sc=dynamic_cover&l=2023122003034909B448A5A66536000675",
"origin_cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-p-0068/41390a6f86074b92b6f2c0e0f7862940_1650477670~tplv-tiktokx-360p.webp?x-expires=1703127600&x-signature=jTNGQhpF1J2xAkQt9Y99KyBUK%2Fs%3D&s=SEARCH&se=false&sh=&sc=feed_cover&l=2023122003034909B448A5A66536000675",
"duration": 42,
"play": "https://v16m.tiktokcdn-us.com/2911fa4f722b77bde13d550ed592bd72/6582ae20/video/tos/useast2a/tos-useast2a-ve-0068c003/386472d4c6fa4b479b2cf07c829bf268/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=3076&bt=1538&bti=NTY6QGo0QHM6OjZANDQuYCMucCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=NDVlPDc0ZDNkZjo7ZGRnaUBpM21rdjw6ZmU3PDMzNzczM0AwMF5jNi0yNV4xNDJiXzAyYSNtNmEucjRvaGRgLS1kMTZzcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00008000",
"wmplay": "https://v16m.tiktokcdn-us.com/9cc6a5d30e0c8948fa294f0027bd0c53/6582ae20/video/tos/useast2a/tos-useast2a-ve-0068c003/9262ec5e631d49e399d3a4bec28db2a7/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=3068&bt=1534&bti=NTY6QGo0QHM6OjZANDQuYCMucCMxNDNg&cs=0&ds=3&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=NTxmOmVoO2g0aDk3ZzlkZkBpM21rdjw6ZmU3PDMzNzczM0A0NDU1XzUuNi4xX15gYDQuYSNtNmEucjRvaGRgLS1kMTZzcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00008000",
"music": "https://sf16-ies-music-sg.tiktokcdn.com/obj/tos-alisg-ve-2774/38acb76cefbb49a187e366d26cf62dcc",
"music_info": {
"id": "6756231813640751105",
"title": "Better Days",
"play": "https://sf16-ies-music-sg.tiktokcdn.com/obj/tos-alisg-ve-2774/38acb76cefbb49a187e366d26cf62dcc",
"cover": "https://p16-sg.tiktokcdn.com/aweme/720x720/tos-alisg-v-2774/f2bad9596ac24f3a891c8fd1d5f1ac14.jpeg",
"author": "",
"original": false,
"duration": 60,
"album": "Better Days"
},
"play_count": 96398,
"digg_count": 6714,
"comment_count": 221,
"share_count": 378,
"download_count": 771,
"create_time": 1650477624,
"anchors": null,
"anchors_extras": "",
"is_ad": false,
"commerce_info": {
"adv_promotable": false,
"auction_ad_invited": false,
"branded_content_type": 0,
"with_comment_filter_words": false
},
"commercial_video_info": "",
"author": {
"id": "6903482507896587266",
"unique_id": "ale_tech_",
"nickname": "Ale_tech",
"avatar": "https://p16-sign-sg.tiktokcdn.com/tos-alisg-avt-0068/81f0a65e38808852ad043323d2ce9f1f~c5_300x300.jpeg?x-expires=1703127600&x-signature=djzLzF0pH%2BAV9a8qJvTZh8WNYJE%3D"
},
"is_top": 0
},
{
"aweme_id": "v07025g50000c1ld595r2810d6r02sr0",
"video_id": "6947597057498680578",
"region": "ID",
"title": "Reply to @efryday1 segini aja ya, klo full sampe instalasi ga cukup \ud83d\ude01 #linux #ubuntu #fyp",
"cover": "https://p16-sign-sg.tiktokcdn.com/tos-alisg-p-0037/64ad02f4cfed4bd6961c1c1c992f7df1_1617613505~tplv-dmt-logom:tos-alisg-i-0000/d375b8ca573c48e18f7f19175091a0a2.image?x-expires=1703127600&x-signature=GcUS%2Bet8IeNtkQKFSCTILJMULwU%3D&s=SEARCH&se=false&sh=&sc=dynamic_cover&l=2023122003034909B448A5A66536000675",
"origin_cover": "https://p16-sign-sg.tiktokcdn.com/tos-alisg-p-0037/f3fb7b0dc7aa4337bcfd1badd15b403e_1617613505~tplv-tiktokx-360p.webp?x-expires=1703127600&x-signature=Zea7B3dtoIkndr3j5gE0ehRiM6g%3D&s=SEARCH&se=false&sh=&sc=feed_cover&l=2023122003034909B448A5A66536000675",
"duration": 31,
"play": "https://v16m.tiktokcdn-us.com/a46680841f4e969f7c489dd568c85b63/6582ae15/video/tos/alisg/tos-alisg-pve-0037/efc3dc88570e4b408f460da5b30fb8cf/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=2154&bt=1077&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=N2lpaGg2ODpmZDM7aGZkOkBpM3YzOTM7dTxnNDMzODgzM0A0Yl9fNDQvXl8xLTEzMS4vYSNvL29hLi8yMmlgLS1kLzRzcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00008000",
"wmplay": "https://v16m.tiktokcdn-us.com/6ec40d75d2efc39bbbc62d727e37dcb5/6582ae15/video/tos/alisg/tos-alisg-pve-0037/a773953f390642158983d23034e27b84/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=2146&bt=1073&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=3&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=MzNnOGk2PDMzaDQ2Nzg8ZUBpM3YzOTM7dTxnNDMzODgzM0AuMTIxMF42Xl4xYl8tYV9fYSNvL29hLi8yMmlgLS1kLzRzcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00008000",
"music": "https://sf16-ies-music-sg.tiktokcdn.com/obj/tos-alisg-ve-2774/o4DXDrZtQBCbzoc9djRgk49nQBelNUnnqBesLO",
"music_info": {
"id": "6936177291449862145",
"title": "Terpesona",
"play": "https://sf16-ies-music-sg.tiktokcdn.com/obj/tos-alisg-ve-2774/o4DXDrZtQBCbzoc9djRgk49nQBelNUnnqBesLO",
"cover": "https://p16-sg.tiktokcdn.com/aweme/720x720/tos-alisg-v-2774/3c59e86071a6488386f0eb8d0520c80e.jpeg",
"author": "",
"original": false,
"duration": 60,
"album": "Terpesona"
},
"play_count": 90199,
"digg_count": 5316,
"comment_count": 138,
"share_count": 56,
"download_count": 329,
"create_time": 1617613504,
"anchors": null,
"anchors_extras": "",
"is_ad": false,
"commerce_info": {
"adv_promotable": false,
"auction_ad_invited": false,
"branded_content_type": 0,
"with_comment_filter_words": false
},
"commercial_video_info": "",
"author": {
"id": "6568018206484742146",
"unique_id": "nvmbrxi",
"nickname": "adit, iya pasaran iya",
"avatar": "https://p16-sign-sg.tiktokcdn.com/tos-alisg-avt-0068/468629c8128b024d5787e670f9b42cf5~c5_300x300.jpeg?x-expires=1703127600&x-signature=t7lOKDBIC5r4n%2BalT1G8j4jKHGQ%3D"
},
"is_top": 0
},
{
"aweme_id": "v09044g40000ciktif3c77ub4lij07ag",
"video_id": "7253570500482321669",
"region": "DO",
"title": "If you love coding leave your \u2764\ufe0f #softwareengineer #softwaredeveloper #focus #fullstackdeveloper #ubuntudesktop #ubuntu #cybersecurity #reactnativedeveloper #androiddev #androiddeveloper #frontenddeveloper credits: mobiledevpro \ud83d\ude4f",
"cover": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/5290f82af0a041f4ae3482161633c5ec_1688853494?x-expires=1703127600&x-signature=Qp1zu10vuLK%2BNq%2FcoElwf7hDO%2Bo%3D&s=SEARCH&se=false&sh=&sc=dynamic_cover&l=2023122003034909B448A5A66536000675",
"origin_cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-p-0068/4c4a0d1a522c47e9bdd2b4e033cba110_1688853493~tplv-tiktokx-360p.webp?x-expires=1703127600&x-signature=zy%2BADnksas488G1HYiROxwdXsKo%3D&s=SEARCH&se=false&sh=&sc=feed_cover&l=2023122003034909B448A5A66536000675",
"duration": 11,
"play": "https://v16m.tiktokcdn-us.com/84404bbc3b0d32ee0baea347f3a1d0ef/6582ae01/video/tos/useast2a/tos-useast2a-ve-0068c001/ocfAhU8g7ZJ81dITBenAQkqsRlkQtB9EPdDTbX/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1368&bt=684&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=NjlpOzxlMzhoOjgzOThpZEBpajptb2U6Zml3bDMzNzczM0BiYC9jYjMuNjIxM2IwMy4yYSNeLWYxcjQwZmhgLS1kMTZzcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00008000",
"wmplay": "https://v16m.tiktokcdn-us.com/a2d5770f1c2b3957e6394ba7426f6d67/6582ae01/video/tos/maliva/tos-maliva-ve-0068c801-us/o4JUKZ6bhM7pCAKIC4rnZCAeej5fUS2y1gmPJH/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1418&bt=709&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=3&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=NTw4Ojk2NWlkODtkaGU4ZEBpajptb2U6Zml3bDMzNzczM0BjYjZgXi5hXzIxMC9fL2AuYSNeLWYxcjQwZmhgLS1kMTZzcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00008000",
"music": "https://sf16-ies-music.tiktokcdn.com/obj/ies-music-aiso/7232735770502859546.mp3",
"music_info": {
"id": "7232735763421596442",
"title": "All my Life Lil Durk DrillyEditz",
"play": "https://sf16-ies-music.tiktokcdn.com/obj/ies-music-aiso/7232735770502859546.mp3",
"cover": "https://p16-sign-useast2a.tiktokcdn.com/tos-useast2a-avt-0068-euttp/b05fa4d33296c39c75fbc760a3f3ec5c~c5_1080x1080.jpeg?x-expires=1703127600&x-signature=8MeRalgtFOdb9QfxLhB2QcnIToo%3D",
"author": "DrillyEditz\ud83e\udd77\ud83c\udffd",
"original": true,
"duration": 16,
"album": ""
},
"play_count": 35581,
"digg_count": 2212,
"comment_count": 76,
"share_count": 26,
"download_count": 3,
"create_time": 1688853492,
"anchors": null,
"anchors_extras": "",
"is_ad": false,
"commerce_info": {
"adv_promotable": false,
"auction_ad_invited": false,
"branded_content_type": 0,
"with_comment_filter_words": false
},
"commercial_video_info": "",
"author": {
"id": "7150316965491721221",
"unique_id": "systemf4iled",
"nickname": "System F4iled",
"avatar": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/742fa96d7edbfd240757b83abf1dee0b~c5_300x300.jpeg?x-expires=1703127600&x-signature=q8rwCWfCZfWurxF6cfYOaSFciNo%3D"
},
"is_top": 0
},
{
"aweme_id": "v09044g40000cbsv2tjc77uftkprfpdg",
"video_id": "7131996966136958213",
"region": "TH",
"title": "Linux > Windows as always #Windows #Windows10 #Linux #Ubuntu #UbuntuMATE #computer #fyp",
"cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-p-0068/421b9598296b4bc6923c82eeb0f74104_1660547458~tplv-dmt-logom:tos-useast2a-v-0068/ab4d5c65921f4556be5bd4218a05e684.image?x-expires=1703127600&x-signature=mKiZ6hytGR6zx7ek4ORgtryE6w8%3D&s=SEARCH&se=false&sh=&sc=dynamic_cover&l=2023122003034909B448A5A66536000675",
"origin_cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-p-0068/e8f1878fc8ba466aa5af013169e33e84_1660547459~tplv-tiktokx-360p.webp?x-expires=1703127600&x-signature=6L2yZY%2FL03UOop%2FV7q7SX1Q64hI%3D&s=SEARCH&se=false&sh=&sc=feed_cover&l=2023122003034909B448A5A66536000675",
"duration": 15,
"play": "https://v19.tiktokcdn-us.com/edc39bdef7b4266e334ae233f724aeb6/6582ae05/video/tos/useast2a/tos-useast2a-pve-0068/4d8f2b0e1f2b4f60ad84a60fa124bfc3/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=3178&bt=1589&bti=NTY6QGo0QHM6OjZANDQuYCMucCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=NWk6O2hkaDdkM2g7ZTg6PEBpanN1bmk6Znd5ZTMzNzczM0AyYl9eLTReXzYxXzYvLTI0YSNhY21xcjRnL3BgLS1kMTZzcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00008000",
"wmplay": "https://v19.tiktokcdn-us.com/c882763d2ef55a77003b1dace8fd7fd1/6582ae05/video/tos/maliva/tos-maliva-ve-0068c801-us/512ba1d0e8824c9087d6aea78ffa3a6b/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=3388&bt=1694&bti=NTY6QGo0QHM6OjZANDQuYCMucCMxNDNg&cs=0&ds=3&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=Ojc5NGk7aTdnM2RkOjQ2ZkBpanN1bmk6Znd5ZTMzNzczM0AtXmJhYC5fXy0xXzItYzRhYSNhY21xcjRnL3BgLS1kMTZzcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00008000",
"music": "https://sf16-ies-music-sg.tiktokcdn.com/obj/tos-alisg-ve-2774/ocvaqFeexInKSUDFFPAAbfDlG4UPzTODg5VBEC",
"music_info": {
"id": "6915348665175526145",
"title": "Can You Feel My Heart",
"play": "https://sf16-ies-music-sg.tiktokcdn.com/obj/tos-alisg-ve-2774/ocvaqFeexInKSUDFFPAAbfDlG4UPzTODg5VBEC",
"cover": "https://p16-sg.tiktokcdn.com/aweme/720x720/tos-alisg-v-2774/b7c28602318840fbbb4f4b0f2ed7ddf9.jpeg",
"author": "",
"original": false,
"duration": 15,
"album": "Can You Feel My Heart"
},
"play_count": 36974,
"digg_count": 1078,
"comment_count": 202,
"share_count": 23,
"download_count": 3,
"create_time": 1660547455,
"anchors": null,
"anchors_extras": "",
"is_ad": false,
"commerce_info": {
"adv_promotable": false,
"auction_ad_invited": false,
"branded_content_type": 0,
"with_comment_filter_words": false
},
"commercial_video_info": "",
"author": {
"id": "6988352212003243009",
"unique_id": "hannor_smith",
"nickname": "han.flac \ud83c\udf1f\ud83c\udfa7\ud83c\udfcd\ufe0f",
"avatar": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/58167255ee1d05d70dffc59ec0286e96~c5_300x300.jpeg?x-expires=1703127600&x-signature=N3KkHMBaqDBqVcHCt833OonyJ58%3D"
},
"is_top": 0
},
{
"aweme_id": "v12044gd0000cb4rr83c77u2coev35og",
"video_id": "7118429570973519150",
"region": "US",
"title": "If you haven't tried Linux yet, Ubuntu should be your first \ud83e\udd79\ud83d\udcbb #linuxdistro #ubuntulinux #oldlaptopmotherboard #oldpchardware #linux",
"cover": "https://p19-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/57a059b3776f4deeaa4a7297dd9d46fa_1657388545?x-expires=1703127600&x-signature=2HaAe082pCt55gBKb9YvXCyITyw%3D&s=SEARCH&se=false&sh=&sc=dynamic_cover&l=2023122003034909B448A5A66536000675",
"origin_cover": "https://p16-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/8680ac0cc03840df9ff6f003cbeb74f6_1657388545~tplv-tiktokx-360p.webp?x-expires=1703127600&x-signature=s3Xz2qsI2um65ZUbw3Lg8SQYo20%3D&s=SEARCH&se=false&sh=&sc=feed_cover&l=2023122003034909B448A5A66536000675",
"duration": 30,
"play": "https://v19.tiktokcdn-us.com/607a59a261b518e59c26873e2d17f22d/6582ae13/video/tos/useast5/tos-useast5-pve-0068-tx/948fbacccb394ae2b722a1e39f3daa59/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1524&bt=762&bti=NTY6QGo0QHM6OjZANDQuYCMucCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=aDY4PDw5OjM1ZWlkNDxpaUBpajh5cjU6Zjt1ZTMzZzczNEAzXi4tYl4uNi0xLjBeNC5hYSNsMGJgcjQwbzFgLS1kMS9zcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00008000",
"wmplay": "https://v19.tiktokcdn-us.com/eee0d5c470e85e78957dc78605caf792/6582ae13/video/tos/useast5/tos-useast5-ve-0068c001-tx/27add3d74a14413091513d4c7a3bd553/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1522&bt=761&bti=NTY6QGo0QHM6OjZANDQuYCMucCMxNDNg&cs=0&ds=3&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=aDppOTdmPDY2OzszaDtnOkBpajh5cjU6Zjt1ZTMzZzczNEBgL2IzNDFhNWAxNWFgYTU1YSNsMGJgcjQwbzFgLS1kMS9zcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00008000",
"music": "https://sf16-ies-music-sg.tiktokcdn.com/obj/tos-alisg-ve-2774/oc1hI8e1jwQ9aPyoBOnCUc6YZwjgZBbt1GDfbY",
"music_info": {
"id": "6778968637492430849",
"title": "Blue Blood",
"play": "https://sf16-ies-music-sg.tiktokcdn.com/obj/tos-alisg-ve-2774/oc1hI8e1jwQ9aPyoBOnCUc6YZwjgZBbt1GDfbY",
"cover": "https://p16-sg.tiktokcdn.com/aweme/720x720/tos-alisg-v-2774/c24e133f8d134031b03a673aa747ed6d.jpeg",
"author": "",
"original": false,
"duration": 60,
"album": "It's Always Sunny In Philadelphia (Music from the Original FX Series)"
},
"play_count": 30464,
"digg_count": 817,
"comment_count": 29,
"share_count": 34,
"download_count": 84,
"create_time": 1657388542,
"anchors": null,
"anchors_extras": "",
"is_ad": false,
"commerce_info": {
"adv_promotable": false,
"auction_ad_invited": false,
"branded_content_type": 0,
"with_comment_filter_words": false
},
"commercial_video_info": "",
"author": {
"id": "7113859000824316974",
"unique_id": "buggintech",
"nickname": "buggintech",
"avatar": "https://p16-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/e390a937f6bc4281817b391361497762~c5_300x300.jpeg?x-expires=1703127600&x-signature=nyeyvxtxVn%2F0SE%2Fn7Wnavk3wGjE%3D"
},
"is_top": 0
},
{
"aweme_id": "v12044gd0000cgi7l53c77u11maehifg",
"video_id": "7216028231185616171",
"region": "US",
"title": "Replying to @fltlnsb I say all this with extreme confidence, as I\u2019ve spent the last 12 years supporting users ages 30-70. People that grew up with computers around, some who have used computers for 20 years. Telling someone \u2018you need to learn an entire new platform and convert your PDF\u2019s with command line because it\u2019s better\u2019 is ridiculous. Can YOU do it faster with CLI? Yes. Does that mean everybody should? Absolutely not. Here in 2023, the vast majority of people in the work force and ones who own business are barely getting by with the Windows computers they have. Microsoft has a chokehold on the end user facing side of business/corporate/enterprise business. Uprooting their infrastructure to implement a product that will change everything they do from a day to day because \u2018it\u2019s better\u2019 - sorry, not a business expense anyone is willing to swallow. You can spend 15 hours a week for a few weeks learning the OS and figuring out whats good about it. Other people do not. Time is money. and is business, just because you csn - doesnt mean you should. #ubuntu #linux #computers #naaackers ",
"cover": "https://p16-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/0485fb4c1f2549c8987ba323ed26993f_1680112511~tplv-dmt-logom:tos-useast5-i-0068-tx/798594786eff41458cbbc4f6da8a1476.image?x-expires=1703127600&x-signature=ZARZhRKsQwA%2FgqGM4p6Y%2BD%2F%2FRpI%3D&s=SEARCH&se=false&sh=&sc=dynamic_cover&l=2023122003034909B448A5A66536000675",
"origin_cover": "https://p16-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/aff34ca418e1453390f96584faae5535_1680112510~tplv-tiktokx-360p.webp?x-expires=1703127600&x-signature=qiKmXCjMnnE%2B8P68jaEydOsTtEU%3D&s=SEARCH&se=false&sh=&sc=feed_cover&l=2023122003034909B448A5A66536000675",
"duration": 58,
"play": "https://v19.tiktokcdn-us.com/ac39a5443137abb7743118c8e6c38046/6582ae30/video/tos/useast5/tos-useast5-ve-0068c002-tx/126f8a804a18420cb9f9a0362aa3ac6c/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1760&bt=880&bti=NTY6QGo0QHM6OjZANDQuYCMucCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=Mzk6NGc7Zzg3NDs2O2Q4OkBpamxocDQ6Zjg6ajMzZzczNEAxNC8wMC1hNmAxLjYwXzI2YSNjZV4ucjQwaWZgLS1kMS9zcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00008000",
"wmplay": "https://v19.tiktokcdn-us.com/d9bb3ef4e9ef24d612514d12c451b6cc/6582ae30/video/tos/useast5/tos-useast5-ve-0068c004-tx/f40dd86127a2452fa9b021f295880c2c/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1862&bt=931&bti=NTY6QGo0QHM6OjZANDQuYCMucCMxNDNg&cs=0&ds=3&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=O2g0aTM1Zjw1NWk7OTZkaEBpamxocDQ6Zjg6ajMzZzczNEBeNTJjNWJfXl4xNDZeYTBfYSNjZV4ucjQwaWZgLS1kMS9zcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00008000",
"music": "https://sf16.tiktokcdn-us.com/obj/ies-music-tx/7216028260373662507.mp3",
"music_info": {
"id": "7216028213967997739",
"title": "original sound - naaackers",
"play": "https://sf16.tiktokcdn-us.com/obj/ies-music-tx/7216028260373662507.mp3",
"cover": "https://p19-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/99794bd713d80b1c2b65aff94b0e6500~c5_1080x1080.jpeg?x-expires=1703127600&x-signature=a6w1ZerLtyqMcKa2H0ioqXBy%2FxQ%3D",
"author": "Naaackers",
"original": true,
"duration": 58,
"album": ""
},
"play_count": 7621,
"digg_count": 528,
"comment_count": 227,
"share_count": 5,
"download_count": 2,
"create_time": 1680112522,
"anchors": [
{
"actions": [
{
"action_type": 1,
"icon": {
"height": 720,
"uri": "tiktok-obj/28px_primary_create_onDark3x.png",
"url_list": [
"https://p16-sg.tiktokcdn.com/obj/tiktok-obj/28px_primary_create_onDark3x.png?biz_tag=anchor.effect"
],
"url_prefix": null,
"width": 720
},
"schema": "shoot"
}
],
"component_key": "anchor_effect",
"description": "Effects",
"extra": "{\"effect_source\":0,\"is_commerce\":0}",
"icon": {
"height": 720,
"uri": "tiktok-obj/20px_anchor_effect3x.png",
"url_list": [
"https://p16-sg.tiktokcdn.com/obj/tiktok-obj/20px_anchor_effect3x.png?biz_tag=anchor.effect"
],
"url_prefix": null,
"width": 720
},
"id": "454747",
"keyword": "Green Screen Video",
"log_extra": "{\"anchor_id\":\"454747\",\"anchor_name\":\"Green Screen Video\",\"anchor_type\":\"prop\"}",
"thumbnail": {
"height": 64,
"uri": "d846c3603d36559c76691965ba808340",
"url_list": [
"https://lf16-effectcdn-va.tiktokcdn.com/obj/ies-fe-effect-va/d846c3603d36559c76691965ba808340",
"https://lf21-effectcdn-va.tiktokcdn.com/obj/ies-fe-effect-va/d846c3603d36559c76691965ba808340",
"https://lf19-effectcdn-va.tiktokcdn.com/obj/ies-fe-effect-va/d846c3603d36559c76691965ba808340"
],
"url_prefix": null,
"width": 64
},
"type": 28
}
],
"anchors_extras": "",
"is_ad": false,
"commerce_info": {
"adv_promotable": false,
"auction_ad_invited": false,
"branded_content_type": 0,
"with_comment_filter_words": false
},
"commercial_video_info": "",
"author": {
"id": "6757790857447539718",
"unique_id": "naaackers",
"nickname": "Naaackers",
"avatar": "https://p19-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/99794bd713d80b1c2b65aff94b0e6500~c5_300x300.jpeg?x-expires=1703127600&x-signature=f7mkopk%2F2EG%2FpA98qcgATY8nj80%3D"
},
"is_top": 0
},
{
"aweme_id": "v0f025gc0000cfaidr3c77u090l3co8g",
"video_id": "7193698776148725019",
"region": "TH",
"title": "Life of a Linux user \ud83d\udc27 #linux #ubuntu #computer #laptop #tech #techtok ",
"cover": "https://p16-sign-useast2a.tiktokcdn.com/obj/tos-useast2a-p-0037-aiso/6fb0705918f0413b9127aa0c2c95c0ed_1674913526?x-expires=1703127600&x-signature=yNngPeJxRl5AB6rIiA3EjO%2BO6Gg%3D&s=SEARCH&se=false&sh=&sc=dynamic_cover&l=2023122003034909B448A5A66536000675",
"origin_cover": "https://p16-sign-useast2a.tiktokcdn.com/tos-useast2a-p-0037-aiso/908c47399ad2445b8720a5c0c49e45fa_1674913529~tplv-tiktokx-360p.webp?x-expires=1703127600&x-signature=yt9L2hrWPZ3Z1mbX9tVT8KDOeB0%3D&s=SEARCH&se=false&sh=&sc=feed_cover&l=2023122003034909B448A5A66536000675",
"duration": 15,
"play": "https://v19.tiktokcdn-us.com/3e418870739bcd9b1a1244bfa89d4b8f/6582ae05/video/tos/useast2a/tos-useast2a-pve-0037-aiso/oMRKA6h7oCSVjVnvBCN2JToIzNA9fhQY2AIxpU/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=2328&bt=1164&bti=NTY6QGo0QHM6OjZANDQuYCMucCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=aGYzNDs5ZDlnNzhmOjk8NEBpanI2MzM6ZnVsaTMzZjgzM0AvMDM0LzQxXjExMGFhNTIyYSM1YGk2cjQwYV5gLS1kL2Nzcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00008000",
"wmplay": "https://v19.tiktokcdn-us.com/1e5ce0481d836bbef971d1c12ebefd60/6582ae05/video/tos/useast2a/tos-useast2a-pve-0037-aiso/o43NInhzZSNh2AoQixpVBYRUbCf2CKA6o2A6Wo/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=2616&bt=1308&bti=NTY6QGo0QHM6OjZANDQuYCMucCMxNDNg&cs=0&ds=3&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=ZTQ1OjRlOTc5NDg5OzRmPEBpanI2MzM6ZnVsaTMzZjgzM0A2MDE2MGJhNmAxMy8uLzYtYSM1YGk2cjQwYV5gLS1kL2Nzcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00008000",
"music": "https://sf16-ies-music-sg.tiktokcdn.com/obj/tos-alisg-ve-2774/oYnmdQz4MBF51IDQzbZptpTBBaALqgveOLC1ek",
"music_info": {
"id": "6969850109240559617",
"title": "Meow",
"play": "https://sf16-ies-music-sg.tiktokcdn.com/obj/tos-alisg-ve-2774/oYnmdQz4MBF51IDQzbZptpTBBaALqgveOLC1ek",
"cover": "https://p16-sg.tiktokcdn.com/aweme/720x720/tos-alisg-v-2774/df24a43905f9413bb3596cb8c765bc16.jpeg",
"author": "",
"original": false,
"duration": 60,
"album": "Meow"
},
"play_count": 29000,
"digg_count": 458,
"comment_count": 31,
"share_count": 9,
"download_count": 1,
"create_time": 1674913523,
"anchors": null,
"anchors_extras": "",
"is_ad": false,
"commerce_info": {
"adv_promotable": false,
"auction_ad_invited": false,
"branded_content_type": 0,
"with_comment_filter_words": false
},
"commercial_video_info": "",
"author": {
"id": "6988352212003243009",
"unique_id": "hannor_smith",
"nickname": "han.flac \ud83c\udf1f\ud83c\udfa7\ud83c\udfcd\ufe0f",
"avatar": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/58167255ee1d05d70dffc59ec0286e96~c5_300x300.jpeg?x-expires=1703127600&x-signature=N3KkHMBaqDBqVcHCt833OonyJ58%3D"
},
"is_top": 0
},
{
"aweme_id": "v12044gd0000c9hnderc77u0gkpj1f80",
"video_id": "7089641605862313262",
"region": "US",
"title": "#linux #sysadmin #systemadministrator #python #fyp\u30b7 #ubuntu #tech #computerscience #networkengineer #linux #bash",
"cover": "https://p16-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx/f4004afdbfb1457fa72e54aeebbae658_1650685820?x-expires=1703127600&x-signature=baO3nkbdG73H%2FLlG%2BB%2B5E7Nff3k%3D&s=SEARCH&se=false&sh=&sc=dynamic_cover&l=2023122003034909B448A5A66536000675",
"origin_cover": "https://p16-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/8995429c7adb447083dfa93330af88bd_1650685820~tplv-tiktokx-360p.webp?x-expires=1703127600&x-signature=CPnQVAqZ%2F2C8tS%2FmgBczNQzDnmM%3D&s=SEARCH&se=false&sh=&sc=feed_cover&l=2023122003034909B448A5A66536000675",
"duration": 47,
"play": "https://v19.tiktokcdn-us.com/0c2c6034ef9b8ebc826a41cd168a3bff/6582ae25/video/tos/useast5/tos-useast5-ve-0068c003-tx/e25fcce499b04eaf97928d4c70f27070/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1502&bt=751&bti=NTY6QGo0QHM6OjZANDQuYCMucCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=M2loZDhkZTMzaDg1OzloOUBpM2ltbjM6ZmhxPDMzZzczNEAvYzEvMDEvXjExLy9gYDQ1YSM1Lm1kcjRvYWVgLS1kMS9zcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00008000",
"wmplay": "https://v19.tiktokcdn-us.com/540ebc82370825c5bb5cac7605ba9166/6582ae25/video/tos/useast5/tos-useast5-pve-0068-tx/a06e405ea9f042eebc7d7d3432a5a3d3/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1498&bt=749&bti=NTY6QGo0QHM6OjZANDQuYCMucCMxNDNg&cs=0&ds=3&ft=kJrRfy7oZNx0PD1J-wtxg9w8HGM5kEeC~&mime_type=video_mp4&qs=0&rc=aTo4N2QzaDxpaDs2OjRlOkBpM2ltbjM6ZmhxPDMzZzczNEA2MC0vMmAtXmAxMGI2XjZjYSM1Lm1kcjRvYWVgLS1kMS9zcw%3D%3D&l=2023122003034909B448A5A66536000675&btag=e00008000",
"music": "https://sf16.tiktokcdn-us.com/obj/ies-music-tx/7089641612577409835.mp3",
"music_info": {
"id": "7089641579396287278",
"title": "original sound - stevenservo",
"play": "https://sf16.tiktokcdn-us.com/obj/ies-music-tx/7089641612577409835.mp3",
"cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/62713abc4ad5f8eb52869addf6611a92~c5_1080x1080.jpeg?x-expires=1703127600&x-signature=x8ba50BVMtG%2BVOppSeHHwPsi2x8%3D",
"author": "StevenServo",
"original": true,
"duration": 47,
"album": ""
},
"play_count": 4083,
"digg_count": 427,
"comment_count": 28,
"share_count": 18,
"download_count": 44,
"create_time": 1650685819,
"anchors": null,
"anchors_extras": "",
"is_ad": false,
"commerce_info": {
"adv_promotable": false,
"auction_ad_invited": false,
"branded_content_type": 0,
"with_comment_filter_words": false
},
"commercial_video_info": "",
"author": {
"id": "6941572883425166342",
"unique_id": "stevenservo",
"nickname": "StevenServo",
"avatar": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/62713abc4ad5f8eb52869addf6611a92~c5_300x300.jpeg?x-expires=1703127600&x-signature=sQFsgOJXOOxGQtHf4cwmfNrSKX4%3D"
},
"is_top": 0
}
],
"cursor": 10,
"hasMore": true
}
}

View File

@@ -1,17 +0,0 @@
import requests
import json
url = "https://tiktok-download-video1.p.rapidapi.com/feedSearch"
querystring = {"keywords":"VS code","count":"10","cursor":"0","region":"US","publish_time":"0","sort_type":"1"}
headers = {
"X-RapidAPI-Key": "YOUR_API_KEY",
"X-RapidAPI-Host": "tiktok-download-video1.p.rapidapi.com"
}
response = requests.get(url, headers=headers, params=querystring)
print(response.json())
with open("./record.json", "w") as f:
json.dump(response.json(), f)

View File

@@ -1,373 +0,0 @@
{
"code": 0,
"msg": "success",
"processed_time": 1.2462,
"data": {
"videos": [
{
"aweme_id": "v09044g40000cdrtb5jc77u48deft16g",
"video_id": "7167433112283008261",
"region": "NL",
"title": "\u201cYou have been misinformed, as much as we\u2019ve been misinformed.\u201d #blackpeople #africancommunity #blackpower #blackunity #panafricanism #fyp #foryou #panafrican #afrikancommunity #blackconsciousness #melanin #africa #blackcommunity #unlearnandrelearn #blackexcellence #problack #knowthyself #ubuntu",
"cover": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/b9093c120b9944fda3f547001d44a240_1668798075?x-expires=1703124000&x-signature=4%2BExSPK3MmTFdQCMYg72mi5HDzg%3D&s=SEARCH&se=false&sh=&sc=dynamic_cover&l=20231220021905B0B31567CD73EF000289",
"origin_cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-p-0068/e124bf8b4c67438b901351498b4b5695_1668798074~tplv-tiktokx-360p.webp?x-expires=1703124000&x-signature=0bJVu2w0UcX9iAgRQW7h%2F%2F4OYSA%3D&s=SEARCH&se=false&sh=&sc=feed_cover&l=20231220021905B0B31567CD73EF000289",
"duration": 40,
"play": "https://v19.tiktokcdn-us.com/cce82f9db7d6c71f833e2b53e2abc39b/6582a3a2/video/tos/useast2a/tos-useast2a-pve-0068/okIE3ejh9BGhBnnpUbp4OJQQD71rNdIWeBWARo/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=2522&bt=1261&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZtc0PD1Agptxg9wp6DhXvEeC~&mime_type=video_mp4&qs=0&rc=NGQ1ZTc2aGY5ZTs3PDtoOUBpajRpZzc6Zjh3ZzMzNzczM0A1YDEwXi00Xi0xMjY0YWBgYSMzcWI1cjRnX29gLS1kMTZzcw%3D%3D&l=20231220021905B0B31567CD73EF000289&btag=e00008000",
"wmplay": "https://v19.tiktokcdn-us.com/0201af034764a80c072969f8d1ebf395/6582a3a2/video/tos/useast2a/tos-useast2a-ve-0068c003/ooYQ9UGW3IjANcl1r8DJBedERBonhQWJnIb9eh/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=2592&bt=1296&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=3&ft=kJrRfy7oZtc0PD1Agptxg9wp6DhXvEeC~&mime_type=video_mp4&qs=0&rc=NTM2ZTZpNjZnNjo7NzxlZUBpajRpZzc6Zjh3ZzMzNzczM0AuNl9fYjYtNS8xLTRjMjIuYSMzcWI1cjRnX29gLS1kMTZzcw%3D%3D&l=20231220021905B0B31567CD73EF000289&btag=e00008000",
"music": "https://sf16-ies-music-va.tiktokcdn.com/obj/musically-maliva-obj/7167433130218556166.mp3",
"music_info": {
"id": "7167433094969821958",
"title": "original sound - afrikancommunity",
"play": "https://sf16-ies-music-va.tiktokcdn.com/obj/musically-maliva-obj/7167433130218556166.mp3",
"cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/93c33be7e299ea8cf1762b3ff08175bc~c5_1080x1080.jpeg?x-expires=1703124000&x-signature=kP1cVyhAwezOqb3NoY3PVxO9Oyo%3D",
"author": "afrikancommunity",
"original": true,
"duration": 40,
"album": ""
},
"play_count": 4059853,
"digg_count": 785436,
"comment_count": 5817,
"share_count": 12996,
"download_count": 11150,
"create_time": 1668798067,
"anchors": null,
"anchors_extras": "",
"is_ad": false,
"commerce_info": {
"adv_promotable": false,
"auction_ad_invited": false,
"branded_content_type": 0,
"with_comment_filter_words": false
},
"commercial_video_info": "",
"author": {
"id": "6813642741462287365",
"unique_id": "afrikancommunity",
"nickname": "afrikancommunity",
"avatar": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/93c33be7e299ea8cf1762b3ff08175bc~c5_300x300.jpeg?x-expires=1703124000&x-signature=QIJZsNfd2YOgDg8lMGEqGQeam24%3D"
},
"is_top": 0
},
{
"aweme_id": "v0f025gc0000cglvmnrc77u4ni1u20bg",
"video_id": "7212008888734878982",
"region": "BW",
"title": "S/O @Fiksology\ud83e\udef4 ka DC\ud83d\udd25@gavin.ubuntu @Jame McVernando #ubuntubandmerch #thamiubuntu #SAMA28 ",
"cover": "https://p16-sign-useast2a.tiktokcdn.com/obj/tos-useast2a-p-0037-aiso/29910b3791a7474babf262da0477c5ee_1680604010?x-expires=1703124000&x-signature=96i6RGuz10acj1SwttTl1FwE9YQ%3D&s=SEARCH&se=false&sh=&sc=dynamic_cover&l=20231220021905B0B31567CD73EF000289",
"origin_cover": "https://p16-sign-useast2a.tiktokcdn.com/tos-useast2a-p-0037-aiso/71a9299ab7a446d39f99542fefcf521a_1680604009~tplv-tiktokx-360p.webp?x-expires=1703124000&x-signature=s%2FD6JYmr8UF3GflvYGTwX%2FhlvVE%3D&s=SEARCH&se=false&sh=&sc=feed_cover&l=20231220021905B0B31567CD73EF000289",
"duration": 22,
"play": "https://v16m.tiktokcdn-us.com/fbc1b7c9d6df457b01e73329c76b5592/6582a390/video/tos/useast2a/tos-useast2a-pve-0037-aiso/oMAXWfuEQQ8FJFNSeEr89paDofng8LACfAbT3g/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1840&bt=920&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZtc0PD1Agptxg9wp6DhXvEeC~&mime_type=video_mp4&qs=4&rc=M2dpNjhkZWY0PDhmaTc4Z0BpajN4bDc6ZnF5ajMzZjgzM0AvYjQvMC9fX2AxL2JhX14vYSNfLy5rcjRvamlgLS1kL2Nzcw%3D%3D&l=20231220021905B0B31567CD73EF000289&btag=e00008000",
"wmplay": "https://v16m.tiktokcdn-us.com/18d13ec31cad5bffc05a1f795a3769aa/6582a390/video/tos/useast2a/tos-useast2a-pve-0037c001-aiso/o4AgX8Cfar83ne9MWbgEfbJSA8DINxHoAQCfQu/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=4390&bt=2195&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=3&ft=kJrRfy7oZtc0PD1Agptxg9wp6DhXvEeC~&mime_type=video_mp4&qs=0&rc=NjNkMzg8NmhkaWg7NDs6ZEBpajN4bDc6ZnF5ajMzZjgzM0BiXzBfX14zXjMxNDMxMjY1YSNfLy5rcjRvamlgLS1kL2Nzcw%3D%3D&l=20231220021905B0B31567CD73EF000289&btag=e00008000",
"music": "https://sf16-ies-music-va.tiktokcdn.com/obj/musically-maliva-obj/7205224029014264582.mp3",
"music_info": {
"id": "7205224038348000005",
"title": "original sound - basiiey_monnapula",
"play": "https://sf16-ies-music-va.tiktokcdn.com/obj/musically-maliva-obj/7205224029014264582.mp3",
"cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/ae049960ea6786673783ce66b36dc7da~c5_1080x1080.jpeg?x-expires=1703124000&x-signature=%2FnY5uAyksD41s%2Bm8ECAFLIAtXfo%3D",
"author": "Basetsana Monnapula",
"original": true,
"duration": 32,
"album": ""
},
"play_count": 6868872,
"digg_count": 673843,
"comment_count": 2832,
"share_count": 8103,
"download_count": 463,
"create_time": 1679176676,
"anchors": null,
"anchors_extras": "",
"is_ad": false,
"commerce_info": {
"adv_promotable": false,
"auction_ad_invited": false,
"branded_content_type": 0,
"with_comment_filter_words": false
},
"commercial_video_info": "",
"author": {
"id": "217487781219024896",
"unique_id": "thami.ubuntu",
"nickname": "Thami Philemon",
"avatar": "https://p16-sign-useast2a.tiktokcdn.com/tos-useast2a-avt-0068-giso/e546ddc5cc70ede037ac5397cd068323~c5_300x300.jpeg?x-expires=1703124000&x-signature=gFNFdUl%2BtBIHAI2sX2ANcjUvqyo%3D"
},
"is_top": 0
},
{
"aweme_id": "v09044g40000cc2hhdbc77ufp0p13l9g",
"video_id": "7135137277650439430",
"region": "NL",
"title": "That that you do most, will be that that you do best #jawanzakunjufu #NBA #blackthinkers #imhotep #blackhistory #blackpeople #africancommunity #blackpower #blackunity #panafricanism #fyp #foryou #panafrican #afrikancommunity #blackconsciousness #blackcommunity #unlearnandrelearn #blackexcellence #problack #knowthyself #ubuntu",
"cover": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/09d637f654744d62826facb8a685a44f_1661278613?x-expires=1703124000&x-signature=85qRg1ex%2FkscTT8sSfRpq7XSvos%3D&s=SEARCH&se=false&sh=&sc=dynamic_cover&l=20231220021905B0B31567CD73EF000289",
"origin_cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-p-0068/bbc3f2523b50459099bec727131b73c0_1661278613~tplv-tiktokx-360p.webp?x-expires=1703124000&x-signature=PIvHym7TJAU%2BXGScgIuFGaRVkzI%3D&s=SEARCH&se=false&sh=&sc=feed_cover&l=20231220021905B0B31567CD73EF000289",
"duration": 60,
"play": "https://v19.tiktokcdn-us.com/1f8f8c36c8e7b1d288c09be54090c378/6582a3b6/video/tos/useast2a/tos-useast2a-ve-0068c002/2a36ec9fd62e44a79fd341682312a92e/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1212&bt=606&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZtc0PD1Agptxg9wp6DhXvEeC~&mime_type=video_mp4&qs=0&rc=Zjs1ZjxlNWY8ZzllNTw5ZkBpam80M2k6ZmdrZjMzNzczM0BjNjBfXzA1NWAxLWAxNDMzYSM2MG1tcjRfZS9gLS1kMTZzcw%3D%3D&l=20231220021905B0B31567CD73EF000289&btag=e00010000",
"wmplay": "https://v19.tiktokcdn-us.com/a48c0af8c7cdf127e4c085c61adfbb89/6582a3b6/video/tos/useast2a/tos-useast2a-pve-0068/7d4538584c7e424a9aa60e034d02c6e8/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1254&bt=627&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=3&ft=kJrRfy7oZtc0PD1Agptxg9wp6DhXvEeC~&mime_type=video_mp4&qs=0&rc=NjVkaDxlZ2c0aWllZ2Q1NkBpam80M2k6ZmdrZjMzNzczM0BeLl4uNmA0X2AxMGFfLy80YSM2MG1tcjRfZS9gLS1kMTZzcw%3D%3D&l=20231220021905B0B31567CD73EF000289&btag=e00010000",
"music": "https://sf16-ies-music-va.tiktokcdn.com/obj/musically-maliva-obj/7135137305857460998.mp3",
"music_info": {
"id": "7135137291257154310",
"title": "original sound - afrikancommunity",
"play": "https://sf16-ies-music-va.tiktokcdn.com/obj/musically-maliva-obj/7135137305857460998.mp3",
"cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/93c33be7e299ea8cf1762b3ff08175bc~c5_1080x1080.jpeg?x-expires=1703124000&x-signature=kP1cVyhAwezOqb3NoY3PVxO9Oyo%3D",
"author": "afrikancommunity",
"original": true,
"duration": 60,
"album": ""
},
"play_count": 3509817,
"digg_count": 406909,
"comment_count": 5287,
"share_count": 27964,
"download_count": 12202,
"create_time": 1661278609,
"anchors": null,
"anchors_extras": "",
"is_ad": false,
"commerce_info": {
"adv_promotable": false,
"auction_ad_invited": false,
"branded_content_type": 0,
"with_comment_filter_words": false
},
"commercial_video_info": "",
"author": {
"id": "6813642741462287365",
"unique_id": "afrikancommunity",
"nickname": "afrikancommunity",
"avatar": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/93c33be7e299ea8cf1762b3ff08175bc~c5_300x300.jpeg?x-expires=1703124000&x-signature=QIJZsNfd2YOgDg8lMGEqGQeam24%3D"
},
"is_top": 0
},
{
"aweme_id": "v09044g40000cbhc463c77u5po59g8fg",
"video_id": "7125471227543670022",
"region": "NL",
"title": "They don\u2019t tell us about ourselves. #blackpeople #africancommunity #blackpower #blackunity #burnaboy #blackhistory #ancientafrica #panafricanism #fyp #foryou #panafrican #afrikancommunity #blackconsciousness #blackcommunity #unlearnandrelearn #blackexcellence #problack #knowthyself #ubuntu",
"cover": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/27058259ac9c4f7996ed15f7d35fcb42_1659028058?x-expires=1703124000&x-signature=zt3tBpttXmjohAqGGMuQZU0Ef%2FU%3D&s=SEARCH&se=false&sh=&sc=dynamic_cover&l=20231220021905B0B31567CD73EF000289",
"origin_cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-p-0068/5b086bdb9aa644448d1219834f894637_1659028057~tplv-tiktokx-360p.webp?x-expires=1703124000&x-signature=0%2BVWKeBo8T%2FHZ2bfJx7bGxsnNUo%3D&s=SEARCH&se=false&sh=&sc=feed_cover&l=20231220021905B0B31567CD73EF000289",
"duration": 43,
"play": "https://v16m.tiktokcdn-us.com/d5828b1ca12fa0904ce987640b5debfc/6582a3a5/video/tos/useast2a/tos-useast2a-ve-0068c003/1dab5ac30d07444fb1115e537e4656fd/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1362&bt=681&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZtc0PD1Agptxg9wp6DhXvEeC~&mime_type=video_mp4&qs=0&rc=aDU7aDVpNWdmOGk6NjhoNkBpajs8cjg6ZjlmZTMzNzczM0BeYmEzYi8xXzExYTYvMi1fYSNjZDJtcjQwMWVgLS1kMTZzcw%3D%3D&l=20231220021905B0B31567CD73EF000289&btag=e00008000",
"wmplay": "https://v16m.tiktokcdn-us.com/b8f7853d30e428a6af5bc705ffeb227d/6582a3a5/video/tos/useast2a/tos-useast2a-ve-0068c004/a8726699cd774e08b10c124967f1095c/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1398&bt=699&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=3&ft=kJrRfy7oZtc0PD1Agptxg9wp6DhXvEeC~&mime_type=video_mp4&qs=0&rc=ZzNlM2k0OWg2OGhpNWVkaUBpajs8cjg6ZjlmZTMzNzczM0AyLmEwL2IvXi4xY2MxMTAuYSNjZDJtcjQwMWVgLS1kMTZzcw%3D%3D&l=20231220021905B0B31567CD73EF000289&btag=e00008000",
"music": "https://sf16-ies-music-va.tiktokcdn.com/obj/musically-maliva-obj/7125471236305849094.mp3",
"music_info": {
"id": "7125471221475527429",
"title": "original sound - afrikancommunity",
"play": "https://sf16-ies-music-va.tiktokcdn.com/obj/musically-maliva-obj/7125471236305849094.mp3",
"cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/93c33be7e299ea8cf1762b3ff08175bc~c5_1080x1080.jpeg?x-expires=1703124000&x-signature=kP1cVyhAwezOqb3NoY3PVxO9Oyo%3D",
"author": "afrikancommunity",
"original": true,
"duration": 43,
"album": ""
},
"play_count": 2663740,
"digg_count": 289424,
"comment_count": 4539,
"share_count": 16995,
"download_count": 62151,
"create_time": 1659028056,
"anchors": null,
"anchors_extras": "",
"is_ad": false,
"commerce_info": {
"adv_promotable": false,
"auction_ad_invited": false,
"branded_content_type": 0,
"with_comment_filter_words": false
},
"commercial_video_info": "",
"author": {
"id": "6813642741462287365",
"unique_id": "afrikancommunity",
"nickname": "afrikancommunity",
"avatar": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/93c33be7e299ea8cf1762b3ff08175bc~c5_300x300.jpeg?x-expires=1703124000&x-signature=QIJZsNfd2YOgDg8lMGEqGQeam24%3D"
},
"is_top": 0
},
{
"aweme_id": "v09044g40000cht15sjc77u6asjve4b0",
"video_id": "7240121196505812229",
"region": "TR",
"title": "#linux #windows #windows10 #windows8 #windows81 #windows7 #windowsvista #windowsxp #microsoft #gnome #macos #apple #arch #ubuntu #comedyvideo #komedi #komik #fyp #kesfet #ke\u015ffet #turkey #turkiye #teknoloji #bili\u015fim #bilisim #pcsystem #2023 #intelcore #bilgisayar #nvidiageforce #gamer #edit #pcbuild #phonk #amdryzen5000series #61 #trabzon ",
"cover": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/912a019391e84924a0feba2bafc5833c_1685722084?x-expires=1703124000&x-signature=L2s%2BqZmvLKnWjgFn1LIQkRqb61M%3D&s=SEARCH&se=false&sh=&sc=dynamic_cover&l=20231220021905B0B31567CD73EF000289",
"origin_cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-p-0068/ee011f41126140cea95ed09e2f928eb9_1685722084~tplv-tiktokx-360p.webp?x-expires=1703124000&x-signature=xo8psLpjw39%2BJiowhbMq8JT5vNs%3D&s=SEARCH&se=false&sh=&sc=feed_cover&l=20231220021905B0B31567CD73EF000289",
"duration": 14,
"play": "https://v16m.tiktokcdn-us.com/6f76e364f63a124e0cecd737216514a4/6582a389/video/tos/useast2a/tos-useast2a-ve-0068c001/o0cgJIJyAXLIhZSIjksCCuf2eDo6Qg80XrGebn/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=3484&bt=1742&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZtc0PD1Agptxg9wp6DhXvEeC~&mime_type=video_mp4&qs=0&rc=ZDQzaTdmO2Q1MzppNjxmZEBpMzd5djk6ZnY0azMzNzczM0AwYF9eLmI2Ni0xMjNjLTJjYSNfYmdecjRnMnFgLS1kMTZzcw%3D%3D&l=20231220021905B0B31567CD73EF000289&btag=e00008000",
"wmplay": "https://v16m.tiktokcdn-us.com/8466b29aabc35b2e0d1cae354b5bc489/6582a389/video/tos/useast2a/tos-useast2a-pve-0068/oUr6jIDrepkDIQtuCAUQEMLbyfBgqeInQ3SCWh/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=3566&bt=1783&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=3&ft=kJrRfy7oZtc0PD1Agptxg9wp6DhXvEeC~&mime_type=video_mp4&qs=0&rc=OztpNzQ1aDNoPDtoOjQ8Z0BpMzd5djk6ZnY0azMzNzczM0AyLTEvLzFiNS0xLjFeMzVjYSNfYmdecjRnMnFgLS1kMTZzcw%3D%3D&l=20231220021905B0B31567CD73EF000289&btag=e00008000",
"music": "https://sf16-ies-music-va.tiktokcdn.com/obj/musically-maliva-obj/7240121223304235782.mp3",
"music_info": {
"id": "7240121203456428805",
"title": "original sound - drizzlep6",
"play": "https://sf16-ies-music-va.tiktokcdn.com/obj/musically-maliva-obj/7240121223304235782.mp3",
"cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/251581f4d1594f04ea2f325ff4cc2a07~c5_1080x1080.jpeg?x-expires=1703124000&x-signature=2LG3zWl2%2B7QD8BPwl4NTwXj2bXw%3D",
"author": "Milli G\u00f6r\u00fc\u015f\u00e7\u00fc Volkan \ud83c\udf3f\ud83c\udf41",
"original": true,
"duration": 15,
"album": ""
},
"play_count": 1704069,
"digg_count": 247851,
"comment_count": 29,
"share_count": 40070,
"download_count": 491,
"create_time": 1685722082,
"anchors": null,
"anchors_extras": "",
"is_ad": false,
"commerce_info": {
"adv_promotable": false,
"auction_ad_invited": false,
"branded_content_type": 0,
"with_comment_filter_words": false
},
"commercial_video_info": "",
"author": {
"id": "7214552603395916805",
"unique_id": "drizzlep6",
"nickname": "Milli G\u00f6r\u00fc\u015f\u00e7\u00fc Volkan \ud83c\udf3f\ud83c\udf41",
"avatar": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/251581f4d1594f04ea2f325ff4cc2a07~c5_300x300.jpeg?x-expires=1703124000&x-signature=VNzWdrg82BH2GPcFrY2f5AOs7OQ%3D"
},
"is_top": 0
},
{
"aweme_id": "v09044g40000cghd6ujc77ucrvaksck0",
"video_id": "7215562429403565317",
"region": "AT",
"title": "would be a shame if i forgot to turn of the network #virtualmachine #vm #virtualbox #windows #linux #ubuntu ",
"cover": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/5fe4d7b7b2524275af1b0e245641ff49_1680004049?x-expires=1703124000&x-signature=xjALXpMVrtWAlD4JQx47n3PLCHw%3D&s=SEARCH&se=false&sh=&sc=dynamic_cover&l=20231220021905B0B31567CD73EF000289",
"origin_cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-p-0068/c9d3e9b708164fe0b78d35b47bba445b_1680004049~tplv-tiktokx-360p.webp?x-expires=1703124000&x-signature=aRETt6Wk39aE1F4zOKWceUGQFKY%3D&s=SEARCH&se=false&sh=&sc=feed_cover&l=20231220021905B0B31567CD73EF000289",
"duration": 7,
"play": "https://v16m.tiktokcdn-us.com/23adf16946df423842b6b0ce4eb05d25/6582a381/video/tos/useast2a/tos-useast2a-pve-0068/okLmMJhUIgoy9xEN90fkidIOo9mVqCA3zAWIDM/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=414&bt=207&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZtc0PD1Agptxg9wp6DhXvEeC~&mime_type=video_mp4&qs=0&rc=NTtnZWk3ZWU5aTlpZTc5PEBpM2ZueWY6ZnhnajMzNzczM0AzXzFiXzEtXzExYWMuMjM2YSNocF5vcjRnM2VgLS1kMTZzcw%3D%3D&l=20231220021905B0B31567CD73EF000289&btag=e00008000",
"wmplay": "https://v16m.tiktokcdn-us.com/5006f2b9180bc17f23aa2c733bd751a2/6582a381/video/tos/useast2a/tos-useast2a-ve-0068c002/o4NDMgNh4oE9y9ok3UAmIOAaY9IPfVC7zMsW0i/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=442&bt=221&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=3&ft=kJrRfy7oZtc0PD1Agptxg9wp6DhXvEeC~&mime_type=video_mp4&qs=0&rc=NWhlNmdoZTRlZDw0NzVmPEBpM2ZueWY6ZnhnajMzNzczM0A1MjAxMWMzNTAxXjAzXmAuYSNocF5vcjRnM2VgLS1kMTZzcw%3D%3D&l=20231220021905B0B31567CD73EF000289&btag=e00008000",
"music": "https://sf16-ies-music-sg.tiktokcdn.com/obj/tos-alisg-ve-2774/94e9293d28a3496981f09994bdb78107",
"music_info": {
"id": "6855250725862115330",
"title": "Coconut Mall (From \"Mario Kart Wii\")",
"play": "https://sf16-ies-music-sg.tiktokcdn.com/obj/tos-alisg-ve-2774/94e9293d28a3496981f09994bdb78107",
"cover": "https://p16-sg.tiktokcdn.com/aweme/720x720/tos-alisg-v-2774/a8f7d19a20714d9f9e8adc4e69d0708c.jpeg",
"author": "",
"original": false,
"duration": 60,
"album": "Mario Kart Wii, The Themes"
},
"play_count": 1526953,
"digg_count": 194938,
"comment_count": 408,
"share_count": 3334,
"download_count": 748,
"create_time": 1680004048,
"anchors": null,
"anchors_extras": "",
"is_ad": false,
"commerce_info": {
"adv_promotable": false,
"auction_ad_invited": false,
"branded_content_type": 0,
"with_comment_filter_words": false
},
"commercial_video_info": "",
"author": {
"id": "6539155051389129743",
"unique_id": "robert_juergerer",
"nickname": "Robert J\u00fcrgerer",
"avatar": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/8a0e92e0a08bbab2c6c25a13a73ddb35~c5_300x300.jpeg?x-expires=1703124000&x-signature=fREAm5jTL1lkhWduDuVUiFZsP9o%3D"
},
"is_top": 0
},
{
"aweme_id": "v09044g40000cbmogv3c77ueej864sc0",
"video_id": "7128504907803397382",
"region": "NL",
"title": "A new type of thinking #malcolmx #blackleaders #blackpeople #africancommunity #blackpower #blackunity #panafricanism #fyp #foryou #panafrican #afrikancommunity #blackconsciousness #blackcommunity #unlearnandrelearn #blackexcellence #problack #knowthyself #ubuntu",
"cover": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/faca00b819ce4e9bad1ec0cb8cbe6d63_1659734391?x-expires=1703124000&x-signature=iuELTcdjUHK1EjDlrDB%2FYRaEdws%3D&s=SEARCH&se=false&sh=&sc=dynamic_cover&l=20231220021905B0B31567CD73EF000289",
"origin_cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-p-0068/13d5e51f74a94aaab819798d08c1f86d_1659734391~tplv-tiktokx-360p.webp?x-expires=1703124000&x-signature=FMv9WMW2COiSjgkJyO437KGKCBU%3D&s=SEARCH&se=false&sh=&sc=feed_cover&l=20231220021905B0B31567CD73EF000289",
"duration": 52,
"play": "https://v19.tiktokcdn-us.com/5a0e715186b2fbfc8132b11429969df5/6582a3ae/video/tos/useast2a/tos-useast2a-pve-0068/352bd236bd0b4a3a8d2213fa503d0a9a/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1208&bt=604&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZtc0PD1Agptxg9wp6DhXvEeC~&mime_type=video_mp4&qs=0&rc=aDNmaTpmZDQ1ZzlkODlnNUBpM3Y5bWg6ZnlyZTMzNzczM0A0MTU0MS4vXmMxMTE1LzQvYSNgMTVicjQwZGpgLS1kMTZzcw%3D%3D&l=20231220021905B0B31567CD73EF000289&btag=e00008000",
"wmplay": "https://v19.tiktokcdn-us.com/76add4e2a195de9c9b2cc8a1fef7b93b/6582a3ae/video/tos/useast2a/tos-useast2a-ve-0068c004/81c10525667444f889c96ca540674e07/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1244&bt=622&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=3&ft=kJrRfy7oZtc0PD1Agptxg9wp6DhXvEeC~&mime_type=video_mp4&qs=0&rc=Nzw1Zjs5OWRoNjU1NmlnZkBpM3Y5bWg6ZnlyZTMzNzczM0BeLzFiXjJjX2ExMzRhMzNjYSNgMTVicjQwZGpgLS1kMTZzcw%3D%3D&l=20231220021905B0B31567CD73EF000289&btag=e00008000",
"music": "https://sf16-ies-music-va.tiktokcdn.com/obj/musically-maliva-obj/7128504954595412741.mp3",
"music_info": {
"id": "7128504937776679686",
"title": "original sound - afrikancommunity",
"play": "https://sf16-ies-music-va.tiktokcdn.com/obj/musically-maliva-obj/7128504954595412741.mp3",
"cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/93c33be7e299ea8cf1762b3ff08175bc~c5_1080x1080.jpeg?x-expires=1703124000&x-signature=kP1cVyhAwezOqb3NoY3PVxO9Oyo%3D",
"author": "afrikancommunity",
"original": true,
"duration": 52,
"album": ""
},
"play_count": 1925864,
"digg_count": 187364,
"comment_count": 4708,
"share_count": 22382,
"download_count": 24979,
"create_time": 1659734390,
"anchors": null,
"anchors_extras": "",
"is_ad": false,
"commerce_info": {
"adv_promotable": false,
"auction_ad_invited": false,
"branded_content_type": 0,
"with_comment_filter_words": false
},
"commercial_video_info": "",
"author": {
"id": "6813642741462287365",
"unique_id": "afrikancommunity",
"nickname": "afrikancommunity",
"avatar": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/93c33be7e299ea8cf1762b3ff08175bc~c5_300x300.jpeg?x-expires=1703124000&x-signature=QIJZsNfd2YOgDg8lMGEqGQeam24%3D"
},
"is_top": 0
},
{
"aweme_id": "v09044g40000ck0isu3c77u97chrtho0",
"video_id": "7278150686989159685",
"region": "ZA",
"title": "#music #amapiano #mellowandsleazy #tmanxpress #SAMA28 ",
"cover": "https://p16-sign-va.tiktokcdn.com/obj/tos-maliva-p-0068/4214065b3b6f4009b2e4808493fbd2d0_1694576516?x-expires=1703124000&x-signature=QyQVCscBgVlZQ8bodf4uS6htdfc%3D&s=SEARCH&se=false&sh=&sc=dynamic_cover&l=20231220021905B0B31567CD73EF000289",
"origin_cover": "https://p16-sign-va.tiktokcdn.com/tos-maliva-p-0068/d06fe6a869dc4f869f18a9e1ed0b3887_1694576523~tplv-tiktokx-360p.webp?x-expires=1703124000&x-signature=e7hRfKon9cvBKqt8Y8%2BkxvIwvlg%3D&s=SEARCH&se=false&sh=&sc=feed_cover&l=20231220021905B0B31567CD73EF000289",
"duration": 59,
"play": "https://v16m.tiktokcdn-us.com/4dbf46e2875addad8d023ad4a99249e7/6582a3b5/video/tos/useast2a/tos-useast2a-ve-0068c003/okMkjuDIQBQl4vAbkXeJCkA9gy5dfEAdnBSRgS/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=1718&bt=859&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=6&ft=kJrRfy7oZtc0PD1Agptxg9wp6DhXvEeC~&mime_type=video_mp4&qs=4&rc=NDxoZmU3Zjc3Njg0aTVlNkBpM2t1Zjw6ZnhsbjMzNzczM0AzYjBiYTEuNmIxNTRhMWAyYSNscWU0cjQwcC1gLS1kMTZzcw%3D%3D&l=20231220021905B0B31567CD73EF000289&btag=e00010000",
"wmplay": "https://v16m.tiktokcdn-us.com/d4b759e76706f51d9400f0c172b11afc/6582a3b5/video/tos/useast2a/tos-useast2a-pve-0068/owIAJI4Endn5SBLxQDb5AZQ7eedkLlBEzSwRHB/?a=1233&ch=0&cr=13&dr=0&lr=all&cd=0%7C0%7C0%7C&cv=1&br=3264&bt=1632&bti=NEBzNTY6QGo6OjZALnAjNDQuYCMxNDNg&cs=0&ds=3&ft=kJrRfy7oZtc0PD1Agptxg9wp6DhXvEeC~&mime_type=video_mp4&qs=0&rc=ODw6PDQ3PDhoOTU1OzppZkBpM2t1Zjw6ZnhsbjMzNzczM0AzLTU0YDQ0XmExMWFeXjFeYSNscWU0cjQwcC1gLS1kMTZzcw%3D%3D&l=20231220021905B0B31567CD73EF000289&btag=e00010000",
"music": "https://sf16-ies-music-sg.tiktokcdn.com/obj/tos-alisg-ve-2774/o0DzM4MSCXFMRonTCAb3e2FgOYIfy1weDJpJSa",
"music_info": {
"id": "7264936944639330305",
"title": "Imnandi lento (feat. Tman Xpress)",
"play": "https://sf16-ies-music-sg.tiktokcdn.com/obj/tos-alisg-ve-2774/o0DzM4MSCXFMRonTCAb3e2FgOYIfy1weDJpJSa",
"cover": "https://p16-sg.tiktokcdn.com/aweme/720x720/tos-alisg-v-2774/oEk3DTvnEAJSBQ6tESAczBCoAZetLQbGugfn7D.jpeg",
"author": "",
"original": false,
"duration": 60,
"album": "Imnandi lento (feat. Tman Xpress)"
},
"play_count": 3087192,
"digg_count": 182514,
"comment_count": 652,
"share_count": 11954,
"download_count": 38,
"create_time": 1694576513,
"anchors": null,
"anchors_extras": "",
"is_ad": true,
"commerce_info": {
"ad_source": 1,
"adv_promotable": true,
"auction_ad_invited": false,
"bc_label_test_text": "Paid partnership",
"branded_content_type": 4,
"with_comment_filter_words": false
},
"commercial_video_info": "",
"author": {
"id": "7276322982490014725",
"unique_id": "ubuntuedits",
"nickname": "UBUNTU",
"avatar": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/e9229e849809078be4bb64b959b4e08d~c5_300x300.jpeg?x-expires=1703124000&x-signature=eRgWDhD7lrp6A7r1IOKMfRggyPY%3D"
},
"is_top": 0
}
],
"cursor": 10,
"hasMore": true
}
}

View File

@@ -1,52 +0,0 @@
import json
import os
from googleapiclient.discovery import build
import socket
socket.setdefaulttimeout(500)
def search_youtube(api_key, query, max_results=50):
youtube = build('youtube', 'v3', developerKey=api_key)
search_response = youtube.search().list(
q=query,
part="id,snippet",
maxResults=max_results,
type="video"
).execute()
videos = []
for search_result in search_response.get("items", []):
if search_result["id"]["kind"] == "youtube#video":
video_id = search_result["id"]["videoId"]
video_metadata = get_video_metadata(api_key, video_id)
videos.append(video_metadata)
return videos
def get_video_metadata(api_key, video_id):
youtube = build('youtube', 'v3', developerKey=api_key)
request = youtube.videos().list(
part="snippet,contentDetails,statistics",
id=video_id
)
response = request.execute()
return response
api_key = 'AIzaSyDI_BBExs-HypVZFxgnR5tj5S6-uKyU4vk' # Replace with your actual API key
# Search for videos related to "VLC player"
vlc_related_videos = search_youtube(api_key, "LibreOffice Impress Tutorial", max_results=10)
# create data folder if not exist
if not os.path.exists("data"):
os.makedirs("data")
for video in vlc_related_videos:
# store the video metadata into a json file
with open(f"data/{video['etag']}.json", "w") as f:
json.dump(video, f, indent=4)

View File

@@ -1,52 +0,0 @@
import json
import os
from googleapiclient.discovery import build
import socket
socket.setdefaulttimeout(500)
def search_youtube(api_key, query, max_results=50):
youtube = build('youtube', 'v3', developerKey=api_key)
search_response = youtube.search().list(
q=query,
part="id,snippet",
maxResults=max_results,
type="video"
).execute()
videos = []
for search_result in search_response.get("items", []):
if search_result["id"]["kind"] == "youtube#video":
video_id = search_result["id"]["videoId"]
video_metadata = get_video_metadata(api_key, video_id)
videos.append(video_metadata)
return videos
def get_video_metadata(api_key, video_id):
youtube = build('youtube', 'v3', developerKey=api_key)
request = youtube.videos().list(
part="snippet,contentDetails,statistics",
id=video_id
)
response = request.execute()
return response
api_key = 'AIzaSyDI_BBExs-HypVZFxgnR5tj5S6-uKyU4vk' # Replace with your actual API key
# Search for videos related to "VLC player"
vlc_related_videos = search_youtube(api_key, "LibreOffice Calc Tutorial", max_results=10)
# create data folder if not exist
if not os.path.exists("data"):
os.makedirs("data")
for video in vlc_related_videos:
# store the video metadata into a json file
with open(f"data/{video['etag']}.json", "w") as f:
json.dump(video, f, indent=4)

View File

@@ -1,52 +0,0 @@
import json
import os
from googleapiclient.discovery import build
import socket
socket.setdefaulttimeout(500)
def search_youtube(api_key, query, max_results=50):
youtube = build('youtube', 'v3', developerKey=api_key)
search_response = youtube.search().list(
q=query,
part="id,snippet",
maxResults=max_results,
type="video"
).execute()
videos = []
for search_result in search_response.get("items", []):
if search_result["id"]["kind"] == "youtube#video":
video_id = search_result["id"]["videoId"]
video_metadata = get_video_metadata(api_key, video_id)
videos.append(video_metadata)
return videos
def get_video_metadata(api_key, video_id):
youtube = build('youtube', 'v3', developerKey=api_key)
request = youtube.videos().list(
part="snippet,contentDetails,statistics",
id=video_id
)
response = request.execute()
return response
api_key = 'AIzaSyDI_BBExs-HypVZFxgnR5tj5S6-uKyU4vk' # Replace with your actual API key
# Search for videos related to "VLC player"
vlc_related_videos = search_youtube(api_key, "Thunderbird Tutorial", max_results=10)
# create data folder if not exist
if not os.path.exists("data"):
os.makedirs("data")
for video in vlc_related_videos:
# store the video metadata into a json file
with open(f"data/{video['etag']}.json", "w") as f:
json.dump(video, f, indent=4)

View File

@@ -1,52 +0,0 @@
import json
import os
from googleapiclient.discovery import build
import socket
socket.setdefaulttimeout(500)
def search_youtube(api_key, query, max_results=50):
youtube = build('youtube', 'v3', developerKey=api_key)
search_response = youtube.search().list(
q=query,
part="id,snippet",
maxResults=max_results,
type="video"
).execute()
videos = []
for search_result in search_response.get("items", []):
if search_result["id"]["kind"] == "youtube#video":
video_id = search_result["id"]["videoId"]
video_metadata = get_video_metadata(api_key, video_id)
videos.append(video_metadata)
return videos
def get_video_metadata(api_key, video_id):
youtube = build('youtube', 'v3', developerKey=api_key)
request = youtube.videos().list(
part="snippet,contentDetails,statistics",
id=video_id
)
response = request.execute()
return response
api_key = 'AIzaSyDI_BBExs-HypVZFxgnR5tj5S6-uKyU4vk' # Replace with your actual API key
# Search for videos related to "VLC player"
vlc_related_videos = search_youtube(api_key, "Ubuntu Desktop Tutorial", max_results=10)
# create data folder if not exist
if not os.path.exists("data"):
os.makedirs("data")
for video in vlc_related_videos:
# store the video metadata into a json file
with open(f"data/{video['etag']}.json", "w") as f:
json.dump(video, f, indent=4)

View File

@@ -1,65 +0,0 @@
import json
import os
from googleapiclient.discovery import build
def search_youtube(api_key, query, max_results=50, language="en"):
youtube = build('youtube', 'v3', developerKey=api_key)
videos = []
next_page_token = None
total_results = 0
while True:
search_response = youtube.search().list(
q=query,
part="id,snippet",
maxResults=max_results,
pageToken=next_page_token,
type="video",
relevanceLanguage=language
).execute()
video_ids = [item['id']['videoId'] for item in search_response.get("items", []) if
item['id']['kind'] == 'youtube#video']
# Fetch metadata for each video
videos.extend([get_video_metadata(api_key, video_id) for video_id in video_ids])
total_results += len(video_ids)
next_page_token = search_response.get('nextPageToken')
if not next_page_token or total_results >= max_results:
break
# Sort videos by view count
sorted_videos = sorted(videos, key=lambda x: int(x['items'][0]['statistics']['viewCount']), reverse=True)
return sorted_videos
def get_video_metadata(api_key, video_id):
youtube = build('youtube', 'v3', developerKey=api_key)
request = youtube.videos().list(
part="snippet,contentDetails,statistics",
id=video_id
)
response = request.execute()
return response
api_key = 'API_KEY' # Replace with your actual API key
# Search for videos related to "VLC player"
vlc_related_videos = search_youtube(api_key, "VLC player", max_results=10)
# create data folder if not exist
if not os.path.exists("data"):
os.makedirs("data")
for video in vlc_related_videos:
# store the video metadata into a json file
with open(f"data/{video['etag']}.json", "w") as f:
json.dump(video, f, indent=4)

2
run.py
View File

@@ -216,6 +216,8 @@ def get_unfinished(action_space, use_model, observation_type, result_dir, total_
domain_path = os.path.join(target_dir, domain)
if os.path.isdir(domain_path):
for example_id in os.listdir(domain_path):
if example_id == "onboard":
continue
example_path = os.path.join(domain_path, example_id)
if os.path.isdir(example_path):
if "result.txt" not in os.listdir(example_path):

Binary file not shown.

Before

Width:  |  Height:  |  Size: 826 KiB

View File

@@ -1,3 +1,3 @@
{
"time_limit": "1800"
"time_limit": "3600"
}

View File

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,350 +0,0 @@
import sys, pathlib;
sys.path.append(str(pathlib.Path(__file__).parents[1]))
import os
import math
import json
import numpy as np
from typing import List
from copy import deepcopy
pynput2pyautogui_key = {
"alt_l": "altleft",
"alt_r": "altright",
}
COMMAND_KEYS = ['accept', 'add', 'alt', 'altleft', 'altright', 'apps', 'backspace', 'browserback', 'browserfavorites', 'browserforward', 'browserhome', 'browserrefresh', 'browsersearch', 'browserstop', 'capslock', 'clear', 'convert', 'ctrl', 'ctrlleft', 'ctrlright', 'decimal', 'del', 'delete', 'divide', 'down', 'end', 'enter', 'esc', 'escape', 'execute', 'f1', 'f10', 'f11', 'f12', 'f13', 'f14', 'f15', 'f16', 'f17', 'f18', 'f19', 'f2', 'f20', 'f21', 'f22', 'f23', 'f24', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'final', 'fn', 'hanguel', 'hangul', 'hanja', 'help', 'home', 'insert', 'junja', 'kana', 'kanji', 'launchapp1', 'launchapp2', 'launchmail', 'launchmediaselect', 'left', 'modechange', 'multiply', 'nexttrack', 'nonconvert', 'num0', 'num1', 'num2', 'num3', 'num4', 'num5', 'num6', 'num7', 'num8', 'num9', 'numlock', 'pagedown', 'pageup', 'pause', 'pgdn', 'pgup', 'playpause', 'prevtrack', 'print', 'printscreen', 'prntscrn', 'prtsc', 'prtscr', 'return', 'right', 'scrolllock', 'select', 'separator', 'shift', 'shiftleft', 'shiftright', 'sleep', 'stop', 'subtract', 'tab', 'up', 'volumedown', 'volumemute', 'volumeup', 'win', 'winleft', 'winright', 'yen', 'command', 'option', 'optionleft', 'optionright', 'alt_l', 'alt_r']
typingkey2str = {
"space" : " ",
}
class DuckTrackEventActionConverter:
def __init__(self, ):
""""""
### Enumerations ###
def move_event_to_action(self, event: dict, action_space: str = "computer_13"):
"""Converts a mouse move event to its corresponding action."""
if action_space == "computer_13":
return {
"action_type": "MOVE_TO",
"parameters": {
"x": event["x"],
"y": event["y"]
}
}
elif action_space == "pyautogui":
return "pyautogui.moveTo({}, {})".format(event["x"], event["y"])
def click_event_to_action(self, event: dict, action_space: str = "computer_13"):
"""Converts a mouse click event to its corresponding action."""
action = {
"action_type": None,
"parameters": {
"button": None
}
}
mouse_button = event["button"]
mouse_pressed = event["pressed"]
if mouse_pressed:
action["action_type"] = "MOUSE_DOWN"
elif not mouse_pressed:
action["action_type"] = "MOUSE_UP"
else:
raise NotImplementedError(mouse_pressed)
if mouse_button in ["left", "right", "middle"]:
action["parameters"]["button"] = mouse_button
else:
raise NotImplementedError(mouse_button)
return action
def press_event_to_action(self, event: dict, action_space: str = "computer_13"):
"""Converts a key down event to its corresponding action."""
# NOTE: the `key down`, `press` have the same meaning here, while different in pyautogui
return {
"action_type": "KEY_DOWN",
"parameters": {
"key": event["name"] if event["name"] not in pynput2pyautogui_key else pynput2pyautogui_key[
event["name"]]
}
}
def release_event_to_action(self, event: dict, action_space: str = "computer_13"):
"""Converts a key release event to its corresponding action."""
return {
"action_type": "KEY_UP",
"parameters": {
"key": event["name"] if event["name"] not in pynput2pyautogui_key else pynput2pyautogui_key[
event["name"]]
}
}
def scroll_event_to_action(self, event: dict, action_space: str = "computer_13"):
"""Converts a scroll event to its corresponding action."""
return {
"action_type": "SCROLL",
"parameters": {
"dx": event["dx"],
"dy": event["dy"]
}
}
def event_to_action(self, event: dict, action_space: str = "computer_13"):
"""Converts an event to its corresponding action based on the event type."""
if event["action"] == "move":
return self.move_event_to_action(event)
elif event["action"] == "click":
return self.click_event_to_action(event)
elif event["action"] == "press":
return self.press_event_to_action(event)
elif event["action"] == "release":
return self.release_event_to_action(event)
elif event["action"] == "scroll":
return self.scroll_event_to_action(event)
else:
raise NotImplementedError(event["action"])
### Compressing ###
def compress_mouse_move(self, data: List[dict], index: int):
"""Compresses consecutive mouse move events into the last move events."""
last_move = data[index]
while index < len(data) and data[index]["action"] == "move":
last_move = data[index]
index += 1
return last_move, index
def compress_scroll(self, data: List[dict], index: int):
"""Compresses consecutive scroll events into a single scroll event."""
last_scroll = data[index]
consecutive_dx, consecutive_dy = data[index]["dx"], data[index]["dy"]
while index < len(data) and data[index]["action"] == "scroll" and np.sign(data[index]["dx"]) == np.sign(consecutive_dx) and np.sign(data[index]["dy"]) == np.sign(consecutive_dy):
last_scroll = data[index]
consecutive_dx += data[index]["dx"]
consecutive_dy += data[index]["dy"]
index += 1
last_scroll["dx"], last_scroll["dy"] = consecutive_dx, consecutive_dy
return last_scroll, index
### Converting ###
def ducktrack_event_file_to_action(self, ducktrack_event_file: str, out_file: str, compress_move: bool = True, compress_scroll: bool = True, compress_click: bool = True,compress_drag: bool = True, compress_press_key: bool = True, compress_typing: bool = True):
"""Converts DuckTrack event data to a list of actions and saves them to a file."""
if not os.path.exists(ducktrack_event_file):
raise FileNotFoundError(ducktrack_event_file)
with open(ducktrack_event_file, 'r') as file:
events = [json.loads(line) for line in file]
# Save the compressed actions in a list
result = []
index = 0
presses_to_skip = 0
releases_to_skip = 0
move_to_skip = 0
keys_pressed = []
# Compress the mouse move events
while index < len(events):
event = events[index]
def do_mouse_press(button: str, _index: int):
num_clicks = 0
mouse_pressed = True
skip_move = 0
click_x, click_y = event["x"], event["y"]
for j, next_event in enumerate(events[index + 1:]):
# make sure the time between mouse clicks is less than 500ms
if next_event["time_stamp"] - event["time_stamp"] > 0.5:
if num_clicks > 0:
if result[-1:][0]["action_type"] == "MOVE_TO":
result.pop()
result.append({
"action_type": "CLICK",
"parameters": {
"button": button,
"x" : click_x,
"y" : click_y,
"num_clicks": num_clicks
}
})
return num_clicks-1, num_clicks, _index, skip_move
break
if "x" in next_event and "y" in next_event:
# if the mouse moves out of the click radius/rectangle, it is not a click sequence
if math.sqrt((next_event["y"] - event["y"]) ** 2 +
(next_event["x"] - event["x"]) ** 2) > 4:
if num_clicks > 0:
if result[-1:][0]["action_type"] == "MOVE_TO":
result.pop()
result.append({
"action_type": "CLICK",
"parameters": {
"button": button,
"x" : click_x,
"y" : click_y,
"num_clicks": num_clicks
}
})
return num_clicks-1, num_clicks, _index, skip_move
break
if next_event["action"] == "click" and compress_click:
if not next_event["pressed"]:
num_clicks += 1
mouse_pressed = False
if num_clicks == 3:
if result[-1:][0]["action_type"] == "MOVE_TO":
result.pop()
result.append({
"action_type": "CLICK",
"parameters": {
"button": button,
"x" : click_x,
"y" : click_y,
"num_clicks": 3
}
})
return 2, 3, _index, skip_move
elif next_event["pressed"]:
mouse_pressed = True
else:
raise NotImplementedError(next_event["pressed"])
elif next_event["action"] != "click" and not mouse_pressed:
if next_event["action"] == "move":
if next_event["x"] == click_x and next_event["y"] == click_y:
skip_move += 1
continue
if result[-1:][0]["action_type"] == "MOVE_TO":
result.pop()
result.append({
"action_type": "CLICK",
"parameters": {
"button": button,
"x" : click_x,
"y" : click_y,
"num_clicks": num_clicks
}
})
return num_clicks-1, num_clicks, _index, skip_move
# Compress {MOUSE_DOWN, MOVE, MOUSE_UP} into DRAG_TO event
elif next_event["action"] == "move" and compress_drag:
if next_event["x"] == click_x and next_event["y"] == click_y:
skip_move += 1
continue
last_move, _index = self.compress_mouse_move(events, _index+1)
result.append({
"action_type": "DRAG_TO",
"parameters": {
"x": last_move["x"],
"y": last_move["y"]
}
})
return 0, 1, _index, skip_move
result.append({
"action_type": "MOUSE_DOWN",
"parameters": {
"button": button
}
})
return 0, 0, _index, skip_move
if event["action"] == "move":
if move_to_skip > 0:
move_to_skip -= 1
index += 1
continue
if compress_move:
last_move, index = self.compress_mouse_move(events, index)
result.extend([self.event_to_action(last_move)])
elif event["action"] == "scroll" and compress_scroll:
last_scroll, index = self.compress_scroll(events, index)
result.extend([self.event_to_action(last_scroll)])
elif event["action"] == "click":
button = event["button"]
if event["pressed"]:
if presses_to_skip == 0:
presses, releases, index, moves = do_mouse_press(button, index)
presses_to_skip += presses
releases_to_skip += releases
move_to_skip += moves
else:
presses_to_skip -= 1
else:
if releases_to_skip == 0:
result.append({
"action_type": "MOUSE_UP",
"parameters": {
"button": button
}
})
else:
releases_to_skip -= 1
index += 1
elif event["action"] == "press" and event["name"] not in COMMAND_KEYS and compress_typing:
typing_words = ""
while index < len(events) and events[index]["action"] in ["press", "release"] and events[index]["name"] not in COMMAND_KEYS:
if events[index]["action"] == "press":
keys_pressed.append(events[index]["name"])
typing_words += events[index]["name"] if events[index]["name"] not in typingkey2str else typingkey2str[events[index]["name"]]
elif events[index]["action"] == "release":
keys_pressed.remove(events[index]["name"])
index += 1
if len(typing_words) > 1:
result.append({
"action_type": "TYPING",
"parameters": {
"text": typing_words
}
})
else:
result.append({
"action_type": "PRESS",
"parameters": {
"key": typing_words
}
})
elif event["action"] == "press" and compress_press_key:
keys_pressed.append(event["name"])
result.append({
"action_type": "PRESS",
"parameters": {
"key": event["name"] if event["name"] not in pynput2pyautogui_key else pynput2pyautogui_key[
event["name"]]
}
})
index += 1
elif event["action"] == "release" and compress_press_key:
keys_pressed.remove(event["name"])
index += 1
else:
result.append(self.event_to_action(event))
index += 1
with open(out_file, "w") as f:
json.dump(result, f)
if __name__ == "__main__":
converter = DuckTrackEventActionConverter()
converter.ducktrack_event_file_to_action(
ducktrack_event_file="complex_clicking.jsonl",
out_file="complex_clicking5.json",
compress_move=True,
compress_scroll=True,
compress_click=True,
compress_drag=True,
compress_press_key=True,
compress_typing=True,
)

View File

@@ -1,111 +0,0 @@
[
{
"action_type": "MOVE_TO",
"parameters": {
"x": 152,
"y": 259
}
},
{
"action_type": "MOUSE_DOWN",
"parameters": {
"button": "left"
}
},
{
"action_type": "MOVE_TO",
"parameters": {
"x": 464,
"y": 317
}
},
{
"action_type": "MOUSE_UP",
"parameters": {
"button": "left"
}
},
{
"action_type": "MOVE_TO",
"parameters": {
"x": 466,
"y": 317
}
},
{
"action_type": "KEY_DOWN",
"parameters": {
"key": "altleft"
}
},
{
"action_type": "KEY_DOWN",
"parameters": {
"key": "="
}
},
{
"action_type": "KEY_UP",
"parameters": {
"key": "="
}
},
{
"action_type": "KEY_UP",
"parameters": {
"key": "altleft"
}
},
{
"action_type": "MOVE_TO",
"parameters": {
"x": 709,
"y": 1047
}
},
{
"action_type": "MOUSE_DOWN",
"parameters": {
"button": "left"
}
},
{
"action_type": "MOVE_TO",
"parameters": {
"x": 709,
"y": 1047
}
},
{
"action_type": "MOUSE_UP",
"parameters": {
"button": "left"
}
},
{
"action_type": "MOVE_TO",
"parameters": {
"x": 717,
"y": 304
}
},
{
"action_type": "MOUSE_DOWN",
"parameters": {
"button": "left"
}
},
{
"action_type": "MOVE_TO",
"parameters": {
"x": 717,
"y": 304
}
},
{
"action_type": "MOUSE_UP",
"parameters": {
"button": "left"
}
}
]

View File

@@ -1,423 +0,0 @@
{"time_stamp": 21028.2899763, "action": "move", "x": 686, "y": 306}
{"time_stamp": 21028.2965794, "action": "move", "x": 684, "y": 306}
{"time_stamp": 21028.3046644, "action": "move", "x": 678, "y": 306}
{"time_stamp": 21028.3126807, "action": "move", "x": 670, "y": 306}
{"time_stamp": 21028.3208329, "action": "move", "x": 661, "y": 306}
{"time_stamp": 21028.3288313, "action": "move", "x": 645, "y": 306}
{"time_stamp": 21028.336626, "action": "move", "x": 625, "y": 306}
{"time_stamp": 21028.3445457, "action": "move", "x": 603, "y": 305}
{"time_stamp": 21028.3527487, "action": "move", "x": 574, "y": 303}
{"time_stamp": 21028.3606394, "action": "move", "x": 544, "y": 301}
{"time_stamp": 21028.3688565, "action": "move", "x": 508, "y": 300}
{"time_stamp": 21028.3768381, "action": "move", "x": 471, "y": 298}
{"time_stamp": 21028.3848709, "action": "move", "x": 430, "y": 296}
{"time_stamp": 21028.3926563, "action": "move", "x": 389, "y": 296}
{"time_stamp": 21028.4009164, "action": "move", "x": 348, "y": 296}
{"time_stamp": 21028.4089388, "action": "move", "x": 313, "y": 296}
{"time_stamp": 21028.4171707, "action": "move", "x": 280, "y": 296}
{"time_stamp": 21028.4245847, "action": "move", "x": 252, "y": 294}
{"time_stamp": 21028.4328148, "action": "move", "x": 225, "y": 294}
{"time_stamp": 21028.4406678, "action": "move", "x": 208, "y": 294}
{"time_stamp": 21028.4486998, "action": "move", "x": 192, "y": 294}
{"time_stamp": 21028.4568529, "action": "move", "x": 177, "y": 294}
{"time_stamp": 21028.4647334, "action": "move", "x": 163, "y": 293}
{"time_stamp": 21028.4729702, "action": "move", "x": 153, "y": 293}
{"time_stamp": 21028.4808044, "action": "move", "x": 143, "y": 293}
{"time_stamp": 21028.4889062, "action": "move", "x": 135, "y": 293}
{"time_stamp": 21028.4967676, "action": "move", "x": 130, "y": 293}
{"time_stamp": 21028.5050544, "action": "move", "x": 124, "y": 293}
{"time_stamp": 21028.5127317, "action": "move", "x": 120, "y": 293}
{"time_stamp": 21028.520827, "action": "move", "x": 117, "y": 293}
{"time_stamp": 21028.5289378, "action": "move", "x": 114, "y": 293}
{"time_stamp": 21028.5371078, "action": "move", "x": 111, "y": 293}
{"time_stamp": 21028.545514, "action": "move", "x": 107, "y": 293}
{"time_stamp": 21028.5527022, "action": "move", "x": 104, "y": 292}
{"time_stamp": 21028.5605384, "action": "move", "x": 100, "y": 292}
{"time_stamp": 21028.5686583, "action": "move", "x": 96, "y": 291}
{"time_stamp": 21028.5766951, "action": "move", "x": 90, "y": 291}
{"time_stamp": 21028.5847502, "action": "move", "x": 85, "y": 291}
{"time_stamp": 21028.5926223, "action": "move", "x": 79, "y": 290}
{"time_stamp": 21028.6007454, "action": "move", "x": 74, "y": 290}
{"time_stamp": 21028.6088707, "action": "move", "x": 70, "y": 289}
{"time_stamp": 21028.6166501, "action": "move", "x": 67, "y": 289}
{"time_stamp": 21028.6249259, "action": "move", "x": 66, "y": 289}
{"time_stamp": 21028.6647889, "action": "move", "x": 66, "y": 289}
{"time_stamp": 21028.6728642, "action": "move", "x": 68, "y": 288}
{"time_stamp": 21028.6807781, "action": "move", "x": 70, "y": 286}
{"time_stamp": 21028.6888295, "action": "move", "x": 74, "y": 285}
{"time_stamp": 21028.6971027, "action": "move", "x": 77, "y": 284}
{"time_stamp": 21028.7046499, "action": "move", "x": 81, "y": 282}
{"time_stamp": 21028.7129405, "action": "move", "x": 86, "y": 281}
{"time_stamp": 21028.7205325, "action": "move", "x": 91, "y": 279}
{"time_stamp": 21028.7285422, "action": "move", "x": 98, "y": 278}
{"time_stamp": 21028.7366509, "action": "move", "x": 104, "y": 275}
{"time_stamp": 21028.7448279, "action": "move", "x": 110, "y": 275}
{"time_stamp": 21028.7527897, "action": "move", "x": 116, "y": 273}
{"time_stamp": 21028.7609718, "action": "move", "x": 120, "y": 272}
{"time_stamp": 21028.7688693, "action": "move", "x": 124, "y": 271}
{"time_stamp": 21028.7766846, "action": "move", "x": 128, "y": 270}
{"time_stamp": 21028.7848371, "action": "move", "x": 131, "y": 270}
{"time_stamp": 21028.7927773, "action": "move", "x": 133, "y": 268}
{"time_stamp": 21028.8007498, "action": "move", "x": 134, "y": 268}
{"time_stamp": 21028.8088143, "action": "move", "x": 136, "y": 268}
{"time_stamp": 21028.8168157, "action": "move", "x": 137, "y": 268}
{"time_stamp": 21028.8246469, "action": "move", "x": 139, "y": 268}
{"time_stamp": 21028.8327817, "action": "move", "x": 140, "y": 268}
{"time_stamp": 21028.8408239, "action": "move", "x": 141, "y": 268}
{"time_stamp": 21028.8488115, "action": "move", "x": 142, "y": 267}
{"time_stamp": 21028.8571578, "action": "move", "x": 143, "y": 267}
{"time_stamp": 21028.8646641, "action": "move", "x": 144, "y": 267}
{"time_stamp": 21028.8741985, "action": "move", "x": 145, "y": 267}
{"time_stamp": 21028.8809717, "action": "move", "x": 146, "y": 267}
{"time_stamp": 21028.8888646, "action": "move", "x": 146, "y": 267}
{"time_stamp": 21028.961049, "action": "move", "x": 146, "y": 266}
{"time_stamp": 21029.0249854, "action": "move", "x": 147, "y": 265}
{"time_stamp": 21029.0328138, "action": "move", "x": 147, "y": 264}
{"time_stamp": 21029.0407582, "action": "move", "x": 147, "y": 264}
{"time_stamp": 21029.0487772, "action": "move", "x": 148, "y": 263}
{"time_stamp": 21029.0569372, "action": "move", "x": 148, "y": 263}
{"time_stamp": 21029.065073, "action": "move", "x": 149, "y": 262}
{"time_stamp": 21029.0729933, "action": "move", "x": 150, "y": 262}
{"time_stamp": 21029.0888149, "action": "move", "x": 150, "y": 261}
{"time_stamp": 21029.0971595, "action": "move", "x": 151, "y": 260}
{"time_stamp": 21029.10458, "action": "move", "x": 151, "y": 260}
{"time_stamp": 21029.1126284, "action": "move", "x": 151, "y": 260}
{"time_stamp": 21029.1208764, "action": "move", "x": 151, "y": 259}
{"time_stamp": 21029.1287413, "action": "move", "x": 152, "y": 259}
{"time_stamp": 21029.1611214, "action": "move", "x": 152, "y": 259}
{"time_stamp": 21029.1614723, "action": "click", "x": 152, "y": 259, "button": "left", "pressed": true}
{"time_stamp": 21029.2168134, "action": "move", "x": 152, "y": 259}
{"time_stamp": 21029.2248681, "action": "move", "x": 154, "y": 259}
{"time_stamp": 21029.2327317, "action": "move", "x": 156, "y": 260}
{"time_stamp": 21029.2408222, "action": "move", "x": 158, "y": 262}
{"time_stamp": 21029.2487515, "action": "move", "x": 163, "y": 263}
{"time_stamp": 21029.2568152, "action": "move", "x": 169, "y": 266}
{"time_stamp": 21029.2649126, "action": "move", "x": 174, "y": 270}
{"time_stamp": 21029.2727425, "action": "move", "x": 183, "y": 273}
{"time_stamp": 21029.2807226, "action": "move", "x": 190, "y": 276}
{"time_stamp": 21029.2887741, "action": "move", "x": 200, "y": 279}
{"time_stamp": 21029.296883, "action": "move", "x": 209, "y": 282}
{"time_stamp": 21029.304834, "action": "move", "x": 220, "y": 285}
{"time_stamp": 21029.3131548, "action": "move", "x": 233, "y": 287}
{"time_stamp": 21029.3207916, "action": "move", "x": 244, "y": 290}
{"time_stamp": 21029.3290871, "action": "move", "x": 256, "y": 292}
{"time_stamp": 21029.3366508, "action": "move", "x": 268, "y": 293}
{"time_stamp": 21029.3445108, "action": "move", "x": 279, "y": 294}
{"time_stamp": 21029.3529213, "action": "move", "x": 288, "y": 297}
{"time_stamp": 21029.3607282, "action": "move", "x": 298, "y": 297}
{"time_stamp": 21029.3691604, "action": "move", "x": 307, "y": 297}
{"time_stamp": 21029.3769931, "action": "move", "x": 316, "y": 298}
{"time_stamp": 21029.3850192, "action": "move", "x": 324, "y": 300}
{"time_stamp": 21029.3927881, "action": "move", "x": 331, "y": 301}
{"time_stamp": 21029.4007925, "action": "move", "x": 336, "y": 302}
{"time_stamp": 21029.4088638, "action": "move", "x": 342, "y": 304}
{"time_stamp": 21029.4167924, "action": "move", "x": 346, "y": 304}
{"time_stamp": 21029.4251047, "action": "move", "x": 349, "y": 304}
{"time_stamp": 21029.4328699, "action": "move", "x": 352, "y": 306}
{"time_stamp": 21029.4409293, "action": "move", "x": 355, "y": 306}
{"time_stamp": 21029.4487136, "action": "move", "x": 356, "y": 307}
{"time_stamp": 21029.4568755, "action": "move", "x": 358, "y": 308}
{"time_stamp": 21029.4647053, "action": "move", "x": 361, "y": 309}
{"time_stamp": 21029.4728173, "action": "move", "x": 363, "y": 310}
{"time_stamp": 21029.4806011, "action": "move", "x": 365, "y": 311}
{"time_stamp": 21029.4889321, "action": "move", "x": 367, "y": 312}
{"time_stamp": 21029.4967544, "action": "move", "x": 370, "y": 313}
{"time_stamp": 21029.5049087, "action": "move", "x": 374, "y": 314}
{"time_stamp": 21029.5129759, "action": "move", "x": 377, "y": 316}
{"time_stamp": 21029.5210278, "action": "move", "x": 381, "y": 317}
{"time_stamp": 21029.5286154, "action": "move", "x": 386, "y": 317}
{"time_stamp": 21029.5371491, "action": "move", "x": 390, "y": 318}
{"time_stamp": 21029.5449815, "action": "move", "x": 393, "y": 319}
{"time_stamp": 21029.5526305, "action": "move", "x": 397, "y": 319}
{"time_stamp": 21029.5604721, "action": "move", "x": 400, "y": 319}
{"time_stamp": 21029.5690371, "action": "move", "x": 402, "y": 319}
{"time_stamp": 21029.5772927, "action": "move", "x": 405, "y": 319}
{"time_stamp": 21029.5846161, "action": "move", "x": 406, "y": 319}
{"time_stamp": 21029.5928399, "action": "move", "x": 407, "y": 319}
{"time_stamp": 21029.6007032, "action": "move", "x": 408, "y": 319}
{"time_stamp": 21029.609118, "action": "move", "x": 409, "y": 319}
{"time_stamp": 21029.6166036, "action": "move", "x": 411, "y": 320}
{"time_stamp": 21029.6249215, "action": "move", "x": 412, "y": 320}
{"time_stamp": 21029.6327262, "action": "move", "x": 414, "y": 320}
{"time_stamp": 21029.6408018, "action": "move", "x": 415, "y": 320}
{"time_stamp": 21029.649463, "action": "move", "x": 418, "y": 320}
{"time_stamp": 21029.6575693, "action": "move", "x": 420, "y": 320}
{"time_stamp": 21029.6650956, "action": "move", "x": 423, "y": 320}
{"time_stamp": 21029.6729346, "action": "move", "x": 426, "y": 320}
{"time_stamp": 21029.6808747, "action": "move", "x": 429, "y": 320}
{"time_stamp": 21029.688616, "action": "move", "x": 432, "y": 320}
{"time_stamp": 21029.6970675, "action": "move", "x": 435, "y": 320}
{"time_stamp": 21029.7049324, "action": "move", "x": 438, "y": 320}
{"time_stamp": 21029.7130458, "action": "move", "x": 439, "y": 320}
{"time_stamp": 21029.7207522, "action": "move", "x": 440, "y": 320}
{"time_stamp": 21029.7289775, "action": "move", "x": 442, "y": 320}
{"time_stamp": 21029.7366577, "action": "move", "x": 443, "y": 320}
{"time_stamp": 21029.7444825, "action": "move", "x": 445, "y": 320}
{"time_stamp": 21029.7526551, "action": "move", "x": 447, "y": 320}
{"time_stamp": 21029.7604951, "action": "move", "x": 448, "y": 320}
{"time_stamp": 21029.7686569, "action": "move", "x": 450, "y": 319}
{"time_stamp": 21029.7775496, "action": "move", "x": 451, "y": 319}
{"time_stamp": 21029.7849685, "action": "move", "x": 451, "y": 319}
{"time_stamp": 21029.7929356, "action": "move", "x": 452, "y": 319}
{"time_stamp": 21029.8007005, "action": "move", "x": 452, "y": 319}
{"time_stamp": 21029.8170717, "action": "move", "x": 453, "y": 319}
{"time_stamp": 21029.8248574, "action": "move", "x": 453, "y": 318}
{"time_stamp": 21029.8330359, "action": "move", "x": 454, "y": 318}
{"time_stamp": 21029.8407804, "action": "move", "x": 454, "y": 318}
{"time_stamp": 21029.8487615, "action": "move", "x": 455, "y": 318}
{"time_stamp": 21029.8648369, "action": "move", "x": 455, "y": 318}
{"time_stamp": 21029.8726477, "action": "move", "x": 456, "y": 318}
{"time_stamp": 21029.8809607, "action": "move", "x": 457, "y": 317}
{"time_stamp": 21029.8888473, "action": "move", "x": 457, "y": 317}
{"time_stamp": 21029.9048933, "action": "move", "x": 458, "y": 317}
{"time_stamp": 21029.9129577, "action": "move", "x": 458, "y": 317}
{"time_stamp": 21029.9208533, "action": "move", "x": 459, "y": 317}
{"time_stamp": 21029.9286645, "action": "move", "x": 459, "y": 317}
{"time_stamp": 21029.9368461, "action": "move", "x": 461, "y": 317}
{"time_stamp": 21029.9448712, "action": "move", "x": 461, "y": 317}
{"time_stamp": 21029.953212, "action": "move", "x": 462, "y": 317}
{"time_stamp": 21029.9608238, "action": "move", "x": 463, "y": 317}
{"time_stamp": 21029.9686821, "action": "move", "x": 463, "y": 317}
{"time_stamp": 21029.9768342, "action": "move", "x": 464, "y": 317}
{"time_stamp": 21030.361149, "action": "move", "x": 464, "y": 317}
{"time_stamp": 21030.3613383, "action": "click", "x": 464, "y": 317, "button": "left", "pressed": false}
{"time_stamp": 21030.9690893, "action": "move", "x": 465, "y": 317}
{"time_stamp": 21030.9770331, "action": "move", "x": 465, "y": 317}
{"time_stamp": 21030.9933165, "action": "move", "x": 466, "y": 317}
{"time_stamp": 21031.8410512, "action": "press", "name": "alt_l"}
{"time_stamp": 21032.1375784, "action": "press", "name": "="}
{"time_stamp": 21032.2331653, "action": "release", "name": "="}
{"time_stamp": 21032.4009051, "action": "release", "name": "alt_l"}
{"time_stamp": 21033.1212821, "action": "move", "x": 466, "y": 317}
{"time_stamp": 21033.1289659, "action": "move", "x": 467, "y": 320}
{"time_stamp": 21033.1370348, "action": "move", "x": 471, "y": 325}
{"time_stamp": 21033.1456134, "action": "move", "x": 475, "y": 332}
{"time_stamp": 21033.1531721, "action": "move", "x": 482, "y": 340}
{"time_stamp": 21033.1605014, "action": "move", "x": 490, "y": 349}
{"time_stamp": 21033.1692663, "action": "move", "x": 498, "y": 359}
{"time_stamp": 21033.1771117, "action": "move", "x": 508, "y": 371}
{"time_stamp": 21033.1850449, "action": "move", "x": 521, "y": 383}
{"time_stamp": 21033.1929826, "action": "move", "x": 535, "y": 399}
{"time_stamp": 21033.201192, "action": "move", "x": 546, "y": 415}
{"time_stamp": 21033.2089185, "action": "move", "x": 555, "y": 434}
{"time_stamp": 21033.216848, "action": "move", "x": 563, "y": 452}
{"time_stamp": 21033.2246769, "action": "move", "x": 570, "y": 469}
{"time_stamp": 21033.2328685, "action": "move", "x": 574, "y": 485}
{"time_stamp": 21033.2407514, "action": "move", "x": 577, "y": 503}
{"time_stamp": 21033.2488102, "action": "move", "x": 578, "y": 518}
{"time_stamp": 21033.2569003, "action": "move", "x": 578, "y": 534}
{"time_stamp": 21033.2654896, "action": "move", "x": 580, "y": 552}
{"time_stamp": 21033.2730147, "action": "move", "x": 580, "y": 571}
{"time_stamp": 21033.2808888, "action": "move", "x": 582, "y": 592}
{"time_stamp": 21033.2890461, "action": "move", "x": 583, "y": 617}
{"time_stamp": 21033.2968868, "action": "move", "x": 586, "y": 643}
{"time_stamp": 21033.3050093, "action": "move", "x": 588, "y": 665}
{"time_stamp": 21033.3129685, "action": "move", "x": 591, "y": 694}
{"time_stamp": 21033.3210515, "action": "move", "x": 592, "y": 716}
{"time_stamp": 21033.3289082, "action": "move", "x": 594, "y": 735}
{"time_stamp": 21033.3368274, "action": "move", "x": 598, "y": 751}
{"time_stamp": 21033.3446464, "action": "move", "x": 601, "y": 761}
{"time_stamp": 21033.3532343, "action": "move", "x": 604, "y": 773}
{"time_stamp": 21033.3607161, "action": "move", "x": 606, "y": 783}
{"time_stamp": 21033.3687129, "action": "move", "x": 608, "y": 794}
{"time_stamp": 21033.3769088, "action": "move", "x": 611, "y": 804}
{"time_stamp": 21033.3846615, "action": "move", "x": 614, "y": 816}
{"time_stamp": 21033.3927661, "action": "move", "x": 617, "y": 826}
{"time_stamp": 21033.4008999, "action": "move", "x": 619, "y": 837}
{"time_stamp": 21033.408732, "action": "move", "x": 621, "y": 846}
{"time_stamp": 21033.4169038, "action": "move", "x": 623, "y": 856}
{"time_stamp": 21033.4250181, "action": "move", "x": 623, "y": 865}
{"time_stamp": 21033.4329144, "action": "move", "x": 624, "y": 875}
{"time_stamp": 21033.4410593, "action": "move", "x": 624, "y": 883}
{"time_stamp": 21033.448994, "action": "move", "x": 626, "y": 891}
{"time_stamp": 21033.4570193, "action": "move", "x": 626, "y": 899}
{"time_stamp": 21033.4648038, "action": "move", "x": 627, "y": 906}
{"time_stamp": 21033.4730101, "action": "move", "x": 628, "y": 913}
{"time_stamp": 21033.4815421, "action": "move", "x": 631, "y": 920}
{"time_stamp": 21033.4891275, "action": "move", "x": 635, "y": 926}
{"time_stamp": 21033.4970011, "action": "move", "x": 639, "y": 930}
{"time_stamp": 21033.5047772, "action": "move", "x": 647, "y": 935}
{"time_stamp": 21033.5132552, "action": "move", "x": 653, "y": 939}
{"time_stamp": 21033.5211245, "action": "move", "x": 659, "y": 943}
{"time_stamp": 21033.5292347, "action": "move", "x": 665, "y": 947}
{"time_stamp": 21033.5373088, "action": "move", "x": 671, "y": 950}
{"time_stamp": 21033.5447875, "action": "move", "x": 677, "y": 955}
{"time_stamp": 21033.5529495, "action": "move", "x": 684, "y": 960}
{"time_stamp": 21033.5609559, "action": "move", "x": 690, "y": 965}
{"time_stamp": 21033.5689335, "action": "move", "x": 696, "y": 971}
{"time_stamp": 21033.5768783, "action": "move", "x": 700, "y": 977}
{"time_stamp": 21033.5846548, "action": "move", "x": 703, "y": 981}
{"time_stamp": 21033.5931357, "action": "move", "x": 705, "y": 985}
{"time_stamp": 21033.6009205, "action": "move", "x": 707, "y": 988}
{"time_stamp": 21033.6088781, "action": "move", "x": 708, "y": 991}
{"time_stamp": 21033.6169713, "action": "move", "x": 709, "y": 994}
{"time_stamp": 21033.6249134, "action": "move", "x": 709, "y": 997}
{"time_stamp": 21033.6328882, "action": "move", "x": 710, "y": 999}
{"time_stamp": 21033.6412016, "action": "move", "x": 711, "y": 1003}
{"time_stamp": 21033.648939, "action": "move", "x": 711, "y": 1007}
{"time_stamp": 21033.6572201, "action": "move", "x": 713, "y": 1010}
{"time_stamp": 21033.6647348, "action": "move", "x": 715, "y": 1013}
{"time_stamp": 21033.6730325, "action": "move", "x": 716, "y": 1017}
{"time_stamp": 21033.6810552, "action": "move", "x": 717, "y": 1021}
{"time_stamp": 21033.6890871, "action": "move", "x": 719, "y": 1024}
{"time_stamp": 21033.6969594, "action": "move", "x": 720, "y": 1026}
{"time_stamp": 21033.7048284, "action": "move", "x": 720, "y": 1028}
{"time_stamp": 21033.7126425, "action": "move", "x": 720, "y": 1028}
{"time_stamp": 21033.7610156, "action": "move", "x": 720, "y": 1029}
{"time_stamp": 21033.7693689, "action": "move", "x": 720, "y": 1029}
{"time_stamp": 21033.7772628, "action": "move", "x": 720, "y": 1030}
{"time_stamp": 21033.7847737, "action": "move", "x": 720, "y": 1031}
{"time_stamp": 21033.7929223, "action": "move", "x": 719, "y": 1031}
{"time_stamp": 21033.801029, "action": "move", "x": 719, "y": 1032}
{"time_stamp": 21033.808944, "action": "move", "x": 718, "y": 1033}
{"time_stamp": 21033.8169394, "action": "move", "x": 717, "y": 1035}
{"time_stamp": 21033.8248771, "action": "move", "x": 716, "y": 1035}
{"time_stamp": 21033.8334548, "action": "move", "x": 716, "y": 1036}
{"time_stamp": 21033.8410779, "action": "move", "x": 715, "y": 1037}
{"time_stamp": 21033.8486117, "action": "move", "x": 715, "y": 1039}
{"time_stamp": 21033.8568906, "action": "move", "x": 713, "y": 1039}
{"time_stamp": 21033.8649249, "action": "move", "x": 712, "y": 1040}
{"time_stamp": 21033.8729566, "action": "move", "x": 712, "y": 1042}
{"time_stamp": 21033.8810286, "action": "move", "x": 711, "y": 1043}
{"time_stamp": 21033.8888454, "action": "move", "x": 711, "y": 1044}
{"time_stamp": 21033.8970736, "action": "move", "x": 709, "y": 1045}
{"time_stamp": 21033.9051884, "action": "move", "x": 709, "y": 1046}
{"time_stamp": 21033.91297, "action": "move", "x": 709, "y": 1047}
{"time_stamp": 21033.9210518, "action": "move", "x": 709, "y": 1047}
{"time_stamp": 21033.9770341, "action": "move", "x": 709, "y": 1047}
{"time_stamp": 21033.9932821, "action": "move", "x": 709, "y": 1047}
{"time_stamp": 21033.9933595, "action": "click", "x": 709, "y": 1047, "button": "left", "pressed": true}
{"time_stamp": 21034.0734669, "action": "move", "x": 709, "y": 1047}
{"time_stamp": 21034.0737272, "action": "click", "x": 709, "y": 1047, "button": "left", "pressed": false}
{"time_stamp": 21034.1450402, "action": "move", "x": 709, "y": 1047}
{"time_stamp": 21034.1608305, "action": "move", "x": 709, "y": 1047}
{"time_stamp": 21034.1690642, "action": "move", "x": 709, "y": 1046}
{"time_stamp": 21034.1770086, "action": "move", "x": 709, "y": 1045}
{"time_stamp": 21034.1849649, "action": "move", "x": 709, "y": 1044}
{"time_stamp": 21034.1927171, "action": "move", "x": 709, "y": 1043}
{"time_stamp": 21034.2008052, "action": "move", "x": 709, "y": 1040}
{"time_stamp": 21034.2088854, "action": "move", "x": 709, "y": 1038}
{"time_stamp": 21034.2167939, "action": "move", "x": 709, "y": 1034}
{"time_stamp": 21034.224882, "action": "move", "x": 709, "y": 1029}
{"time_stamp": 21034.2327267, "action": "move", "x": 711, "y": 1023}
{"time_stamp": 21034.2408131, "action": "move", "x": 711, "y": 1016}
{"time_stamp": 21034.2502186, "action": "move", "x": 712, "y": 1005}
{"time_stamp": 21034.256732, "action": "move", "x": 713, "y": 991}
{"time_stamp": 21034.2646169, "action": "move", "x": 716, "y": 976}
{"time_stamp": 21034.2729272, "action": "move", "x": 719, "y": 955}
{"time_stamp": 21034.2813953, "action": "move", "x": 722, "y": 929}
{"time_stamp": 21034.2889074, "action": "move", "x": 723, "y": 899}
{"time_stamp": 21034.2971538, "action": "move", "x": 725, "y": 871}
{"time_stamp": 21034.3049341, "action": "move", "x": 727, "y": 838}
{"time_stamp": 21034.3130394, "action": "move", "x": 727, "y": 805}
{"time_stamp": 21034.3208269, "action": "move", "x": 728, "y": 771}
{"time_stamp": 21034.3289492, "action": "move", "x": 728, "y": 742}
{"time_stamp": 21034.3367866, "action": "move", "x": 728, "y": 714}
{"time_stamp": 21034.3446895, "action": "move", "x": 728, "y": 686}
{"time_stamp": 21034.3528319, "action": "move", "x": 728, "y": 662}
{"time_stamp": 21034.3606113, "action": "move", "x": 728, "y": 643}
{"time_stamp": 21034.3686987, "action": "move", "x": 727, "y": 620}
{"time_stamp": 21034.3766536, "action": "move", "x": 725, "y": 605}
{"time_stamp": 21034.3847084, "action": "move", "x": 722, "y": 589}
{"time_stamp": 21034.3930586, "action": "move", "x": 719, "y": 576}
{"time_stamp": 21034.4009346, "action": "move", "x": 716, "y": 565}
{"time_stamp": 21034.4090089, "action": "move", "x": 712, "y": 554}
{"time_stamp": 21034.416996, "action": "move", "x": 710, "y": 544}
{"time_stamp": 21034.4246653, "action": "move", "x": 708, "y": 536}
{"time_stamp": 21034.4331124, "action": "move", "x": 707, "y": 527}
{"time_stamp": 21034.4410156, "action": "move", "x": 706, "y": 519}
{"time_stamp": 21034.4488925, "action": "move", "x": 705, "y": 509}
{"time_stamp": 21034.4568042, "action": "move", "x": 705, "y": 500}
{"time_stamp": 21034.4650783, "action": "move", "x": 704, "y": 492}
{"time_stamp": 21034.472962, "action": "move", "x": 703, "y": 483}
{"time_stamp": 21034.4809251, "action": "move", "x": 703, "y": 475}
{"time_stamp": 21034.4889399, "action": "move", "x": 703, "y": 467}
{"time_stamp": 21034.4968154, "action": "move", "x": 703, "y": 460}
{"time_stamp": 21034.505111, "action": "move", "x": 703, "y": 454}
{"time_stamp": 21034.5128327, "action": "move", "x": 703, "y": 446}
{"time_stamp": 21034.5211697, "action": "move", "x": 704, "y": 439}
{"time_stamp": 21034.5291453, "action": "move", "x": 704, "y": 432}
{"time_stamp": 21034.53683, "action": "move", "x": 704, "y": 428}
{"time_stamp": 21034.5453754, "action": "move", "x": 705, "y": 423}
{"time_stamp": 21034.5531997, "action": "move", "x": 705, "y": 419}
{"time_stamp": 21034.5610828, "action": "move", "x": 705, "y": 417}
{"time_stamp": 21034.568917, "action": "move", "x": 705, "y": 414}
{"time_stamp": 21034.5768693, "action": "move", "x": 705, "y": 412}
{"time_stamp": 21034.5849601, "action": "move", "x": 706, "y": 409}
{"time_stamp": 21034.5930116, "action": "move", "x": 706, "y": 406}
{"time_stamp": 21034.6006017, "action": "move", "x": 706, "y": 404}
{"time_stamp": 21034.6086777, "action": "move", "x": 706, "y": 402}
{"time_stamp": 21034.6167229, "action": "move", "x": 706, "y": 400}
{"time_stamp": 21034.6251342, "action": "move", "x": 706, "y": 398}
{"time_stamp": 21034.6325694, "action": "move", "x": 706, "y": 396}
{"time_stamp": 21034.6407476, "action": "move", "x": 706, "y": 393}
{"time_stamp": 21034.6489079, "action": "move", "x": 707, "y": 390}
{"time_stamp": 21034.6567719, "action": "move", "x": 707, "y": 388}
{"time_stamp": 21034.6648437, "action": "move", "x": 707, "y": 386}
{"time_stamp": 21034.6735978, "action": "move", "x": 707, "y": 383}
{"time_stamp": 21034.6808034, "action": "move", "x": 707, "y": 381}
{"time_stamp": 21034.6887831, "action": "move", "x": 707, "y": 379}
{"time_stamp": 21034.6968931, "action": "move", "x": 707, "y": 377}
{"time_stamp": 21034.7048123, "action": "move", "x": 707, "y": 375}
{"time_stamp": 21034.7127621, "action": "move", "x": 706, "y": 373}
{"time_stamp": 21034.7208214, "action": "move", "x": 706, "y": 372}
{"time_stamp": 21034.7289712, "action": "move", "x": 705, "y": 371}
{"time_stamp": 21034.7366015, "action": "move", "x": 705, "y": 370}
{"time_stamp": 21034.7449792, "action": "move", "x": 705, "y": 369}
{"time_stamp": 21034.7528215, "action": "move", "x": 705, "y": 368}
{"time_stamp": 21034.7611243, "action": "move", "x": 705, "y": 367}
{"time_stamp": 21034.7689338, "action": "move", "x": 705, "y": 366}
{"time_stamp": 21034.7768638, "action": "move", "x": 705, "y": 365}
{"time_stamp": 21034.7849091, "action": "move", "x": 705, "y": 364}
{"time_stamp": 21034.792848, "action": "move", "x": 705, "y": 363}
{"time_stamp": 21034.8010344, "action": "move", "x": 705, "y": 362}
{"time_stamp": 21034.809155, "action": "move", "x": 704, "y": 362}
{"time_stamp": 21034.8166183, "action": "move", "x": 704, "y": 359}
{"time_stamp": 21034.8249556, "action": "move", "x": 704, "y": 358}
{"time_stamp": 21034.8333238, "action": "move", "x": 704, "y": 356}
{"time_stamp": 21034.8410045, "action": "move", "x": 703, "y": 354}
{"time_stamp": 21034.8486685, "action": "move", "x": 703, "y": 352}
{"time_stamp": 21034.857368, "action": "move", "x": 703, "y": 350}
{"time_stamp": 21034.8647224, "action": "move", "x": 703, "y": 347}
{"time_stamp": 21034.8730798, "action": "move", "x": 703, "y": 346}
{"time_stamp": 21034.8809692, "action": "move", "x": 703, "y": 342}
{"time_stamp": 21034.8889165, "action": "move", "x": 703, "y": 341}
{"time_stamp": 21034.8969094, "action": "move", "x": 704, "y": 339}
{"time_stamp": 21034.9052672, "action": "move", "x": 704, "y": 337}
{"time_stamp": 21034.9145868, "action": "move", "x": 704, "y": 335}
{"time_stamp": 21034.9208561, "action": "move", "x": 704, "y": 334}
{"time_stamp": 21034.928931, "action": "move", "x": 704, "y": 333}
{"time_stamp": 21034.9374176, "action": "move", "x": 704, "y": 332}
{"time_stamp": 21034.9451258, "action": "move", "x": 704, "y": 330}
{"time_stamp": 21034.9528709, "action": "move", "x": 704, "y": 329}
{"time_stamp": 21034.9611476, "action": "move", "x": 704, "y": 328}
{"time_stamp": 21034.968991, "action": "move", "x": 704, "y": 327}
{"time_stamp": 21034.9768394, "action": "move", "x": 705, "y": 325}
{"time_stamp": 21034.9848553, "action": "move", "x": 705, "y": 324}
{"time_stamp": 21034.993121, "action": "move", "x": 705, "y": 323}
{"time_stamp": 21035.0007992, "action": "move", "x": 706, "y": 322}
{"time_stamp": 21035.0088762, "action": "move", "x": 707, "y": 320}
{"time_stamp": 21035.0166123, "action": "move", "x": 707, "y": 320}
{"time_stamp": 21035.0247724, "action": "move", "x": 708, "y": 318}
{"time_stamp": 21035.0335071, "action": "move", "x": 708, "y": 317}
{"time_stamp": 21035.0411458, "action": "move", "x": 709, "y": 317}
{"time_stamp": 21035.0491997, "action": "move", "x": 709, "y": 316}
{"time_stamp": 21035.0569637, "action": "move", "x": 711, "y": 314}
{"time_stamp": 21035.06496, "action": "move", "x": 711, "y": 313}
{"time_stamp": 21035.0726588, "action": "move", "x": 712, "y": 312}
{"time_stamp": 21035.0807214, "action": "move", "x": 713, "y": 311}
{"time_stamp": 21035.0888078, "action": "move", "x": 713, "y": 309}
{"time_stamp": 21035.0972443, "action": "move", "x": 713, "y": 309}
{"time_stamp": 21035.1048868, "action": "move", "x": 714, "y": 308}
{"time_stamp": 21035.1127551, "action": "move", "x": 715, "y": 307}
{"time_stamp": 21035.1208842, "action": "move", "x": 715, "y": 306}
{"time_stamp": 21035.1285261, "action": "move", "x": 715, "y": 306}
{"time_stamp": 21035.1366862, "action": "move", "x": 715, "y": 305}
{"time_stamp": 21035.1446592, "action": "move", "x": 716, "y": 305}
{"time_stamp": 21035.1528109, "action": "move", "x": 716, "y": 305}
{"time_stamp": 21035.1848109, "action": "move", "x": 716, "y": 304}
{"time_stamp": 21035.208994, "action": "move", "x": 717, "y": 304}
{"time_stamp": 21035.2571327, "action": "move", "x": 717, "y": 304}
{"time_stamp": 21035.2573543, "action": "click", "x": 717, "y": 304, "button": "left", "pressed": true}
{"time_stamp": 21035.3377191, "action": "move", "x": 717, "y": 304}
{"time_stamp": 21035.3379572, "action": "click", "x": 717, "y": 304, "button": "left", "pressed": false}

View File

@@ -1,34 +0,0 @@
import cv2
from matplotlib import pyplot as plt
# Load the image
image = cv2.imread('../../mm_agents/stackoverflow.png')
# Convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Apply adaptive thresholding to get a binary image
thresh = cv2.adaptiveThreshold(
gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2
)
# Find contours
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Filter out contours that are not of cell size
# This is done by assuming that cells will have a relatively standard size
# The size filter is just a placeholder, real values depend on the actual image size
min_cell_size = 500
max_cell_size = 5000
cell_contours = [cnt for cnt in contours if min_cell_size < cv2.contourArea(cnt) < max_cell_size]
# Draw contours on the image
contour_output = image.copy()
cv2.drawContours(contour_output, cell_contours, -1, (0, 255, 0), 2)
# Display the image with cell contours
plt.figure(figsize=(12,6))
plt.imshow(cv2.cvtColor(contour_output, cv2.COLOR_BGR2RGB))
plt.title('Spreadsheet with Cell Contours')
plt.axis('off')
plt.show()

View File

@@ -1,32 +0,0 @@
from PIL import Image, ImageDraw
def mark_point(image_path: str, x: int, y: int, radius: int = 5, color: str = 'red') -> str:
"""
Mark a point on an image and save the image.
"""
# Load the image
image = Image.open(image_path)
# Create a draw object
draw = ImageDraw.Draw(image)
# Draw a small circle to mark the point
draw.ellipse((x - radius, y - radius, x + radius, y + radius), fill=color, outline=color)
# Save the image with the point marked
marked_image_path = image_path[:-4] + '_marked' + image_path[-4:]
image.save(marked_image_path)
return marked_image_path
if __name__ == '__main__':
image_path = 'chrome_start.png'
x = 100
y = 200
radius = 30
color = 'red'
marked_image_path = mark_point(image_path, x, y, radius, color)
print(f"Marked image saved to {marked_image_path}")

View File

@@ -1,32 +0,0 @@
from PIL import Image, ImageDraw
def mark_point(image_path: str, x: int, y: int, radius: int = 5, color: str = 'red') -> str:
"""
Mark a point on an image and save the image.
"""
# Load the image
image = Image.open(image_path)
# Create a draw object
draw = ImageDraw.Draw(image)
# Draw a small circle to mark the point
draw.ellipse((x - radius, y - radius, x + radius, y + radius), fill=color, outline=color)
# Save the image with the point marked
marked_image_path = image_path[:-4] + '_marked' + image_path[-4:]
image.save(marked_image_path)
return marked_image_path
if __name__ == '__main__':
image_path = 'chrome_start.png'
x = 100
y = 200
radius = 30
color = 'red'
marked_image_path = mark_point(image_path, x, y, radius, color)
print(f"Marked image saved to {marked_image_path}")

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,30 +0,0 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/naming-convention": [
"warn",
{
"selector": "import",
"format": [ "camelCase", "PascalCase" ]
}
],
"@typescript-eslint/semi": "warn",
"curly": "warn",
"eqeqeq": "warn",
"no-throw-literal": "warn",
"semi": "off"
},
"ignorePatterns": [
"out",
"dist",
"**/*.d.ts"
]
}

View File

@@ -1,5 +0,0 @@
import { defineConfig } from '@vscode/test-cli';
export default defineConfig({
files: 'out/test/**/*.test.js',
});

View File

@@ -1,8 +0,0 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"dbaeumer.vscode-eslint",
"ms-vscode.extension-test-runner"
]
}

View File

@@ -1,21 +0,0 @@
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"preLaunchTask": "${defaultBuildTask}"
}
]
}

Some files were not shown because too many files have changed in this diff Show More